]> git.neil.brown.name Git - wiggle.git/blob - vpatch.c
Add heuristic to take shortcut when too slow.
[wiggle.git] / vpatch.c
1 /*
2  * wiggle - apply rejected patches
3  *
4  * Copyright (C) 2005 Neil Brown <neilb@cse.unsw.edu.au>
5  * Copyright (C) 2010-2013 Neil Brown <neilb@suse.de>
6  *
7  *
8  *    This program is free software; you can redistribute it and/or modify
9  *    it under the terms of the GNU General Public License as published by
10  *    the Free Software Foundation; either version 2 of the License, or
11  *    (at your option) any later version.
12  *
13  *    This program is distributed in the hope that it will be useful,
14  *    but WITHOUT ANY WARRANTY; without even the implied warranty of
15  *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  *    GNU General Public License for more details.
17  *
18  *    You should have received a copy of the GNU General Public License
19  *    along with this program.
20  *
21  *    Author: Neil Brown
22  *    Email: <neilb@suse.de>
23  */
24
25 /*
26  * vpatch - visual front end for wiggle - aka Browse mode.
27  *
28  * "files" display, lists all files with statistics
29  *    - can hide various lines including subdirectories
30  *      and files without wiggles or conflicts
31  * "merge" display shows various views of  merged file with different
32  *  parts in different colours.
33  *
34  *  The window can be split horizontally to show the original and result
35  *  beside the diff, and each different branch can be shown alone.
36  *
37  */
38
39 #include "wiggle.h"
40 #include <curses.h>
41 #include <unistd.h>
42 #include <stdlib.h>
43 #include <signal.h>
44 #include <fcntl.h>
45 #include <ctype.h>
46 #include <sys/wait.h>
47
48 static void term_init(int raw);
49
50 static int intr_kills = 0;
51
52 /* global attributes */
53 unsigned int a_delete, a_added, a_common, a_sep, a_void,
54         a_unmatched, a_extra, a_already;
55 unsigned int a_has_conflicts, a_has_wiggles, a_no_wiggles, a_saved;
56
57 /******************************************************************
58  * Help window
59  * We display help in an insert, leaving 5 columns left and right,
60  * and 2 rows top and bottom, but at most 58x15 plus border
61  * In help mode:
62  *   SPC or RTN moves down or to next page
63  *   BKSPC goes backwards
64  *   'q' returns to origin screen
65  *   '?' show help on help
66  *   left and right scroll help view
67  *
68  * A help text is an array of lines of text
69  */
70
71 char *help_help[] = {
72         "   You are viewing the help page for the help viewer.",
73         "You normally get here by typing '?'",
74         "",
75         "The following keystrokes work in the help viewer:",
76         "  ?     display this help message",
77         "  q     return to previous view",
78         "  SPC   move forward through help document",
79         "  RTN   same as SPC",
80         "  BKSP  move backward through help document",
81         "  RIGHT scroll help window so text on the right appears",
82         "  LEFT  scroll help window so text on the left appears",
83         NULL
84 };
85
86 char *help_missing[] = {
87         "The file that this patch applies to appears",
88         "to be missing.",
89         "Please type 'q' to continue",
90         NULL
91 };
92
93 char *help_corrupt[] = {
94         "This patch appears to be corrupt",
95         "Please type 'q' to continue",
96         NULL
97 };
98
99 /* We can give one or two pages to display in the help window.
100  * The first is specific to the current context.  The second
101  * is optional and  may provide help in a more broad context.
102  */
103 static int help_window(char *page1[], char *page2[], int query)
104 {
105         int rows, cols;
106         int top, left;
107         int r, c;
108         int ch;
109         char **page = page1;
110         int line = 0;
111         int shift = 0;
112
113         getmaxyx(stdscr, rows, cols);
114
115         if (cols < 70) {
116                 left = 6;
117                 cols = cols-12;
118         } else {
119                 left = (cols-58)/2 - 1;
120                 cols = 58;
121         }
122
123         if (rows < 21) {
124                 top = 3;
125                 rows = rows - 6;
126         } else {
127                 top = (rows-15)/2 - 1;
128                 rows = 15;
129         }
130
131         /* Draw a border around the 'help' area */
132         (void)attrset(A_STANDOUT);
133         for (c = left; c < left+cols; c++) {
134                 mvaddch(top-1, c, '-');
135                 mvaddch(top+rows, c, '-');
136         }
137         for (r = top; r < top + rows ; r++) {
138                 mvaddch(r, left-1, '|');
139                 mvaddch(r, left+cols, '|');
140         }
141         mvaddch(top-1, left-1, '/');
142         mvaddch(top-1, left+cols,  '\\');
143         mvaddch(top+rows, left-1, '\\');
144         mvaddch(top+rows, left+cols, '/');
145         if (query) {
146                 mvaddstr(top-1, left + cols/2 - 4, "Question");
147                 mvaddstr(top+rows, left + cols/2 - 9,
148                          "Answer Y, N, or Q.");
149         } else {
150                 mvaddstr(top-1, left + cols/2 - 9,
151                          "HELP - 'q' to exit");
152                 mvaddstr(top+rows, left+cols/2 - 17,
153                          "Press SPACE for more, '?' for help");
154         }
155         (void)attrset(A_NORMAL);
156
157         while (1) {
158                 char **lnp = page + line;
159
160                 /* Draw as much of the page at the current offset
161                  * as fits.
162                  */
163                 for (r = 0; r < rows; r++) {
164                         char *ln = *lnp;
165                         int sh = shift;
166                         if (ln)
167                                 lnp++;
168                         else
169                                 ln = "";
170
171                         while (*ln && sh > 0) {
172                                 ln++;
173                                 sh--;
174                         }
175                         for (c = 0; c < cols; c++) {
176                                 int chr = *ln;
177                                 if (chr)
178                                         ln++;
179                                 else
180                                         chr = ' ';
181                                 mvaddch(top+r, left+c, chr);
182                         }
183                 }
184                 move(top+rows-1, left);
185                 ch = getch();
186
187                 switch (ch) {
188                 case 'C' - 64:
189                 case 'Q':
190                 case 'q':
191                         return -1;
192                         break;
193                 case 'Y':
194                 case 'y':
195                         if (query)
196                                 return 1;
197                         break;
198                 case 'N':
199                 case 'n':
200                         if (query)
201                                 return 0;
202                         break;
203
204                 case '?':
205                         if (page1 != help_help)
206                                 help_window(help_help, NULL, 0);
207                         break;
208                 case ' ':
209                 case '\r': /* page-down */
210                         for (r = 0; r < rows-2; r++)
211                                 if (page[line])
212                                         line++;
213                         if (!page[line] && !query) {
214                                 line = 0;
215                                 if (page == page1)
216                                         page = page2;
217                                 else
218                                         page = NULL;
219                                 if (page == NULL)
220                                         return -1;
221                         }
222                         break;
223
224                 case '\b': /* page up */
225                         if (line > 0) {
226                                 line -= (rows-2);
227                                 if (line < 0)
228                                         line = 0;
229                         } else {
230                                 if (page == page2)
231                                         page = page1;
232                                 else
233                                         page = page2;
234                                 if (page == NULL)
235                                         page = page1;
236                                 line = 0;
237                         }
238                         break;
239
240                 case KEY_LEFT:
241                         if (shift > 0)
242                                 shift--;
243                         break;
244                 case KEY_RIGHT:
245                         shift++;
246                         break;
247
248                 case KEY_UP:
249                         if (line > 0)
250                                 line--;
251                         break;
252                 case KEY_DOWN:
253                         if (page[line])
254                                 line++;
255                         break;
256                 }
257         }
258 }
259
260 static char *typenames[] = {
261         [End] = "End",
262         [Unmatched] = "Unmatched",
263         [Unchanged] = "Unchanged",
264         [Extraneous] = "Extraneous",
265         [Changed] = "Changed",
266         [Conflict] = "Conflict",
267         [AlreadyApplied] = "AlreadyApplied",
268 };
269
270 /* When we merge the original and the diff together we need
271  * to keep track of where everything came from.
272  * When we display the different views, we need to be able to
273  * select certain portions of the whole document.
274  * These flags are used to identify what is present, and to
275  * request different parts be extracted.  They also help
276  * guide choice of colour.
277  */
278 #define BEFORE  1
279 #define AFTER   2
280 #define ORIG    4
281 #define RESULT  8
282 #define CHANGES 16 /* A change is visible here,
283                     * so 2 streams need to be shown */
284 #define WIGGLED 32 /* a conflict that was successfully resolved */
285 #define CONFLICTED 64 /* a conflict that was not successfully resolved */
286
287 /* Displaying a Merge.
288  * The first step is to linearise the merge.  The merge in inherently
289  * parallel with before/after streams.  However much of the whole document
290  * is linear as normally much of the original in unchanged.
291  * All parallelism comes from the patch.  This normally produces two
292  * parallel stream, but in the case of a conflict can produce three.
293  * For browsing the merge we only ever show two alternates in-line.
294  * When there are three we use two panes with 1 or 2 alternates in each.
295  * So to linearise the two streams we find lines that are completely
296  * unchanged (same for all 3 streams, or missing in 2nd and 3rd) which bound
297  * a region where there are changes.  We include everything between
298  * these twice, in two separate passes.  The exact interpretation of the
299  * passes is handled at a higher level but will be one of:
300  *  original and result
301  *  before and after
302  *  original and after (for a conflict)
303  * This is all encoded in the 'struct merge'.  An array of these describes
304  * the whole document.
305  *
306  * At any position in the merge we can be in one of 3 states:
307  *  0: unchanged section
308  *  1: first pass
309  *  2: second pass
310  *
311  * So to walk a merge in display order we need a position in the merge,
312  * a current state, and when in a changed section, we need to know the
313  * bounds of that changed section.
314  * This is all encoded in 'struct mpos'.
315  *
316  * Each location may or may not be visible depending on certain
317  * display options.
318  *
319  * Also, some locations might be 'invalid' in that they don't need to be displayed.
320  * For example when the patch leaves a section of the original unchanged,
321  * we only need to see the original - the before/after sections are treated
322  * as invalid and are not displayed.
323  * The visibility of newlines is crucial and guides the display.  One line
324  * of displayed text is all the visible sections between two visible newlines.
325  *
326  * Counting lines is a bit tricky.  We only worry about line numbers in the
327  * original (stream 0) as these could compare with line numbers mentioned in
328  * patch chunks.
329  * We count 2 for every line: 1 for everything before the newline and 1 for the newline.
330  * That way we don't get a full counted line until we see the first char after the
331  * newline, so '+' lines are counted with the previous line.
332  *
333  */
334 struct mp {
335         int m; /* merger index */
336         int s; /* stream 0,1,2 for a,b,c */
337         int o; /* offset in that stream */
338         int lineno; /* Counts newlines in stream 0
339                      * set lsb when see newline.
340                      * add one when not newline and lsb set
341                      */
342 };
343 struct mpos {
344         struct mp p, /* the current point (end of a line) */
345                 lo, /* eol for start of the current group */
346                 hi; /* eol for end of the current group */
347         int state; /*
348                     * 0 if on an unchanged (lo/hi not meaningful)
349                     * 1 if on the '-' of a diff,
350                     * 2 if on the '+' of a diff
351                     */
352 };
353
354 struct cursor {
355         struct mp pos;  /* where in the document we are (an element)  */
356         int offset;     /* which char in that element */
357         int target;     /* display column - or -1 if we are looking for 'pos' */
358         int col;        /* where we found pos or target */
359         int width;      /* Size of char, for moving to the right */
360         int alt;        /* Cursor is in alternate window */
361 };
362
363 /* used for checking location during search */
364 static int same_mp(struct mp a, struct mp b)
365 {
366         return a.m == b.m &&
367                 a.s == b.s &&
368                 a.o == b.o;
369 }
370 static int same_mpos(struct mpos a, struct mpos b)
371 {
372         return same_mp(a.p, b.p) &&
373                 (a.state == b.state || a.state == 0 || b.state == 0);
374 }
375
376 /* Check if a particular stream is meaningful in a particular merge
377  * section.  e.g. in an Unchanged section, only stream 0, the
378  * original, is meaningful.  This is used to avoid walking down
379  * pointless paths.
380  */
381 static int stream_valid(int s, enum mergetype type)
382 {
383         switch (type) {
384         case End:
385                 return 1;
386         case Unmatched:
387                 return s == 0;
388         case Unchanged:
389                 return s == 0;
390         case Extraneous:
391                 return s == 2;
392         case Changed:
393                 return s != 1;
394         case Conflict:
395                 return 1;
396         case AlreadyApplied:
397                 return 1;
398         }
399         return 0;
400 }
401
402 /*
403  * Advance the 'pos' in the current mergepos returning the next
404  * element (word).
405  * This walks the merges in sequence, and the streams within
406  * each merge.
407  */
408 static struct elmnt next_melmnt(struct mp *pos,
409                                 struct file fm, struct file fb, struct file fa,
410                                 struct merge *m)
411 {
412         pos->o++;
413         while (pos->m < 0 || m[pos->m].type != End) {
414                 int l = 0; /* Length remaining in current merge section */
415                 if (pos->m >= 0)
416                         switch (pos->s) {
417                         case 0:
418                                 l = m[pos->m].al;
419                                 break;
420                         case 1:
421                                 l = m[pos->m].bl;
422                                 break;
423                         case 2:
424                                 l = m[pos->m].cl;
425                                 break;
426                         }
427                 if (pos->o >= l) {
428                         /* Offset has reached length, choose new stream or
429                          * new merge */
430                         pos->o = 0;
431                         do {
432                                 pos->s++;
433                                 if (pos->s > 2) {
434                                         pos->s = 0;
435                                         pos->m++;
436                                 }
437                         } while (!stream_valid(pos->s, m[pos->m].oldtype));
438                 } else
439                         break;
440         }
441         if (pos->m == -1 || m[pos->m].type == End) {
442                 struct elmnt e;
443                 e.start = NULL; e.hash = 0; e.len = 0;
444                 return e;
445         }
446         switch (pos->s) {
447         default: /* keep compiler happy */
448         case 0:
449                 if (pos->lineno & 1)
450                         pos->lineno++;
451                 if (ends_line(fm.list[m[pos->m].a + pos->o]))
452                         pos->lineno++;
453                 return fm.list[m[pos->m].a + pos->o];
454         case 1: return fb.list[m[pos->m].b + pos->o];
455         case 2: return fa.list[m[pos->m].c + pos->o];
456         }
457 }
458
459 /* step current position.p backwards */
460 static struct elmnt prev_melmnt(struct mp *pos,
461                                 struct file fm, struct file fb, struct file fa,
462                                 struct merge *m)
463 {
464         if (pos->s == 0) {
465                 if (m[pos->m].a + pos->o < fm.elcnt &&
466                     ends_line(fm.list[m[pos->m].a + pos->o]))
467                         pos->lineno--;
468                 if (pos->lineno & 1)
469                         pos->lineno--;
470         }
471
472         pos->o--;
473         while (pos->m >= 0 && pos->o < 0) {
474                 do {
475                         pos->s--;
476                         if (pos->s < 0) {
477                                 pos->s = 2;
478                                 pos->m--;
479                         }
480                 } while (pos->m >= 0 &&
481                          !stream_valid(pos->s, m[pos->m].oldtype));
482                 if (pos->m >= 0) {
483                         switch (pos->s) {
484                         case 0:
485                                 pos->o = m[pos->m].al-1;
486                                 break;
487                         case 1:
488                                 pos->o = m[pos->m].bl-1;
489                                 break;
490                         case 2:
491                                 pos->o = m[pos->m].cl-1;
492                                 break;
493                         }
494                 }
495         }
496         if (pos->m < 0 || m[pos->m].type == End) {
497                 struct elmnt e;
498                 e.start = NULL; e.hash = 0; e.len = 0;
499                 return e;
500         }
501         switch (pos->s) {
502         default: /* keep compiler happy */
503         case 0: return fm.list[m[pos->m].a + pos->o];
504         case 1: return fb.list[m[pos->m].b + pos->o];
505         case 2: return fa.list[m[pos->m].c + pos->o];
506         }
507 }
508
509 /* 'visible' not only checks if this stream in this merge should be
510  * visible in this mode, but also chooses which colour/highlight to use
511  * to display it.
512  */
513 static int visible(int mode, struct merge *m, struct mpos *pos)
514 {
515         enum mergetype type;
516         int stream = pos->p.s;
517
518         if (mode == 0)
519                 return -1;
520         if (pos->p.m < 0)
521                 type = End;
522         else if (mode & RESULT)
523                 type = m[pos->p.m].type;
524         else
525                 type = m[pos->p.m].oldtype;
526         /* mode can be any combination of ORIG RESULT BEFORE AFTER */
527         switch (type) {
528         case End: /* The END is always visible */
529                 return A_NORMAL;
530         case Unmatched: /* Visible in ORIG and RESULT */
531                 if (mode & (ORIG|RESULT))
532                         return a_unmatched;
533                 break;
534         case Unchanged: /* visible everywhere, but only show stream 0 */
535                 if (stream == 0)
536                         return a_common;
537                 break;
538         case Extraneous: /* stream 2 is visible in BEFORE and AFTER */
539                 if ((mode & (BEFORE|AFTER))
540                     && stream == 2)
541                         return a_extra;
542                 break;
543         case Changed: /* stream zero visible ORIG and BEFORE, stream 2 elsewhere */
544                 if (stream == 0 &&
545                     (mode & (ORIG|BEFORE)))
546                         return a_delete;
547                 if (stream == 2 &&
548                     (mode & (RESULT|AFTER)))
549                         return a_added;
550                 break;
551         case Conflict:
552                 switch (stream) {
553                 case 0:
554                         if (mode & ORIG)
555                                 return a_unmatched | A_REVERSE;
556                         break;
557                 case 1:
558                         if (mode & BEFORE)
559                                 return a_extra | A_UNDERLINE;
560                         break;
561                 case 2:
562                         if (mode & (AFTER|RESULT))
563                                 return a_added | A_UNDERLINE;
564                         break;
565                 }
566                 break;
567         case AlreadyApplied:
568                 switch (stream) {
569                 case 0:
570                         if (mode & (ORIG|RESULT))
571                                 return a_already;
572                         break;
573                 case 1:
574                         if (mode & BEFORE)
575                                 return a_delete | A_UNDERLINE;
576                         break;
577                 case 2:
578                         if (mode & AFTER)
579                                 return a_added | A_UNDERLINE;
580                         break;
581                 }
582                 break;
583         }
584         return -1;
585 }
586
587 /* checkline creates a summary of the sort of changes that
588  * are in a line, returning an "or" of
589  *  CHANGES
590  *  WIGGLED
591  *  CONFLICTED
592  */
593 static int check_line(struct mpos pos, struct file fm, struct file fb,
594                       struct file fa,
595                       struct merge *m, int mode)
596 {
597         int rv = 0;
598         struct elmnt e;
599         int unmatched = 0;
600
601         if (pos.p.m < 0)
602                 return 0;
603         do {
604                 int type = m[pos.p.m].oldtype;
605                 if (mode & RESULT)
606                         type = m[pos.p.m].type;
607                 if (type == Changed)
608                         rv |= CHANGES;
609                 else if (type == Conflict) {
610                         rv |= CONFLICTED | CHANGES;
611                 } else if (type == AlreadyApplied) {
612                         rv |= CONFLICTED;
613                         if (mode & (BEFORE|AFTER))
614                                 rv |= CHANGES;
615                 } else if (type == Extraneous) {
616                         if (fb.list[m[pos.p.m].b].start[0] == '\0') {
617                                 /* hunk headers don't count as wiggles
618                                  * and nothing before a hunk header
619                                  * can possibly be part of this 'line' */
620                                 e.start = NULL;
621                                 break;
622                         } else
623                                 rv |= WIGGLED;
624                 } else if (type == Unmatched)
625                         unmatched = 1;
626
627                 if (m[pos.p.m].in_conflict > 1)
628                         rv |= CONFLICTED | CHANGES;
629                 if (m[pos.p.m].in_conflict == 1 &&
630                     (pos.p.o < m[pos.p.m].lo ||
631                      pos.p.o > m[pos.p.m].hi))
632                         rv |= CONFLICTED | CHANGES;
633                 e = prev_melmnt(&pos.p, fm, fb, fa, m);
634         } while (e.start != NULL &&
635                  (!ends_line(e)
636                   || visible(mode, m, &pos) == -1));
637         /* This is a bit of a hack...  If the end-of-line just
638          * before this line was changed, then quite possibly this
639          * line is part of a change too.  This is particularly important
640          * when --ignore-blanks is in effect as newlines are not separate
641          * from other words.  It could be that this test needs to be
642          * strengthened when I have examined more cases.
643          */
644         if (e.start && m[pos.p.m].oldtype == Changed)
645                 rv |= CHANGES;
646
647         if (unmatched && (rv & CHANGES))
648                 rv |= WIGGLED;
649         return rv;
650 }
651
652 /* Find the next line in the merge which is visible.
653  * If we hit the end of a conflicted set during pass-1
654  * we rewind for pass-2.
655  * 'mode' tells which bits we want to see, possible one of
656  * the 4 parts (before/after/orig/result) or one of the pairs
657  * before+after or orig+result.
658  */
659 static void next_mline(struct mpos *pos, struct file fm, struct file fb,
660                        struct file fa,
661                        struct merge *m, int mode)
662 {
663         int mask;
664         do {
665                 struct mp prv;
666                 int mode2;
667
668                 prv = pos->p;
669                 while (1) {
670                         struct elmnt e = next_melmnt(&pos->p, fm, fb, fa, m);
671                         if (e.start == NULL)
672                                 break;
673                         if (ends_line(e) &&
674                             visible(mode, m, pos) >= 0)
675                                 break;
676                 }
677                 mode2 = check_line(*pos, fm, fb, fa, m, mode);
678
679                 if ((mode2 & CHANGES) && pos->state == 0) {
680                         /* Just entered a diff-set */
681                         pos->lo = pos->p;
682                         pos->state = 1;
683                 } else if (!(mode2 & CHANGES) && pos->state) {
684                         /* Come to the end of a diff-set */
685                         switch (pos->state) {
686                         case 1:
687                                 /* Need to record the end */
688                                 pos->hi = prv;
689                                 /* time for another pass */
690                                 pos->p = pos->lo;
691                                 pos->state++;
692                                 break;
693                         case 2:
694                                 /* finished final pass */
695                                 pos->state = 0;
696                                 break;
697                         }
698                 }
699                 mask = ORIG|RESULT|BEFORE|AFTER;
700                 switch (pos->state) {
701                 case 1:
702                         mask &= ~(RESULT|AFTER);
703                         break;
704                 case 2:
705                         mask &= ~(ORIG|BEFORE);
706                         break;
707                 }
708         } while (visible(mode&mask, m, pos) < 0);
709
710 }
711
712 /* Move to previous line - simply the reverse of next_mline */
713 static void prev_mline(struct mpos *pos, struct file fm, struct file fb,
714                        struct file fa,
715                        struct merge *m, int mode)
716 {
717         int mask;
718         do {
719                 struct mp prv;
720                 int mode2;
721
722                 prv = pos->p;
723                 if (pos->p.m < 0)
724                         return;
725                 while (1) {
726                         struct elmnt e = prev_melmnt(&pos->p, fm, fb, fa, m);
727                         if (e.start == NULL)
728                                 break;
729                         if (ends_line(e) &&
730                             visible(mode, m, pos) >= 0)
731                                 break;
732                 }
733                 mode2 = check_line(*pos, fm, fb, fa, m, mode);
734
735                 if ((mode2 & CHANGES) && pos->state == 0) {
736                         /* Just entered a diff-set */
737                         pos->hi = pos->p;
738                         pos->state = 2;
739                 } else if (!(mode2 & CHANGES) && pos->state) {
740                         /* Come to the end (start) of a diff-set */
741                         switch (pos->state) {
742                         case 1:
743                                 /* finished final pass */
744                                 pos->state = 0;
745                                 break;
746                         case 2:
747                                 /* Need to record the start */
748                                 pos->lo = prv;
749                                 /* time for another pass */
750                                 pos->p = pos->hi;
751                                 pos->state--;
752                                 break;
753                         }
754                 }
755                 mask = ORIG|RESULT|BEFORE|AFTER;
756                 switch (pos->state) {
757                 case 1:
758                         mask &= ~(RESULT|AFTER);
759                         break;
760                 case 2:
761                         mask &= ~(ORIG|BEFORE);
762                         break;
763                 }
764         } while (visible(mode&mask, m, pos) < 0);
765 }
766
767 /* blank a whole row of display */
768 static void blank(int row, int start, int cols, unsigned int attr)
769 {
770         (void)attrset(attr);
771         move(row, start);
772         while (cols-- > 0)
773                 addch(' ');
774 }
775
776 /* search of a string on one display line.  If found, update the
777  * cursor.
778  */
779
780 static int mcontains(struct mpos pos,
781                      struct file fm, struct file fb, struct file fa,
782                      struct merge *m,
783                      int mode, char *search, struct cursor *curs,
784                      int dir, int ignore_case)
785 {
786         /* See if any of the files, between start of this line and here,
787          * contain the search string.
788          * However this is modified by dir:
789          *  -2: find last match *before* curs
790          *  -1: find last match at-or-before curs
791          *   1: find first match at-or-after curs
792          *   2: find first match *after* curs
793          *
794          * We only test for equality with curs, so if it is on a different
795          * line it will not be found and everything is before/after.
796          * As we search from end-of-line to start we find the last
797          * match first.
798          * For a forward search, we stop when we find curs.
799          * For a backward search, we forget anything found when we find curs.
800          */
801         struct elmnt e;
802         int found = 0;
803         struct mp mp;
804         int o = 0;
805         int len = strlen(search);
806
807         do {
808                 e = prev_melmnt(&pos.p, fm, fb, fa, m);
809                 if (e.start && e.start[0]) {
810                         int i;
811                         int curs_i;
812                         if (same_mp(pos.p, curs->pos))
813                                 curs_i = curs->offset;
814                         else
815                                 curs_i = -1;
816                         for (i = e.len-1; i >= 0; i--) {
817                                 if (i == curs_i && dir == -1)
818                                         /* next match is the one we want */
819                                         found = 0;
820                                 if (i == curs_i && dir == 2)
821                                         /* future matches not accepted */
822                                         goto break_while;
823                                 if ((!found || dir > 0) &&
824                                     (ignore_case ? strncasecmp : strncmp)
825                                     (e.start+i, search, len) == 0) {
826                                         mp = pos.p;
827                                         o = i;
828                                         found = 1;
829                                 }
830                                 if (i == curs_i && dir == -2)
831                                         /* next match is the one we want */
832                                         found = 0;
833                                 if (i == curs_i && dir == 1)
834                                         /* future matches not accepted */
835                                         goto break_while;
836                         }
837                 }
838         } while (e.start != NULL &&
839                  (!ends_line(e)
840                   || visible(mode, m, &pos) == -1));
841 break_while:
842         if (found) {
843                 curs->pos = mp;
844                 curs->offset = o;
845         }
846         return found;
847 }
848
849 /* Drawing the display window.
850  * There are 7 different ways we can display the data, each
851  * of which can be configured by a keystroke:
852  *  o   original - just show the original file with no changes, but still
853  *                 with highlights of what is changed or unmatched
854  *  r   result   - show just the result of the merge.  Conflicts just show
855  *                 the original, not the before/after options
856  *  b   before   - show the 'before' stream of the patch
857  *  a   after    - show the 'after' stream of the patch
858  *  d   diff     - show just the patch, both before and after
859  *  m   merge    - show the full merge with -+ sections for changes.
860  *                 If point is in a wiggled or conflicted section the
861  *                 window is split horizontally and the diff is shown
862  *                 in the bottom window
863  *  | sidebyside - two panes, left and right.  Left holds the merge,
864  *                 right holds the diff.  In the case of a conflict,
865  *                 left holds orig/after, right holds before/after
866  *
867  * The horizontal split for 'merge' mode is managed as follows.
868  * - The window is split when we first visit a line that contains
869  *   a wiggle or a conflict, and the second pane is removed when
870  *   we next visit a line that contains no changes (is fully Unchanged).
871  * - to display the second pane, we find a visible end-of-line in the
872  *   (BEFORE|AFTER) mode at-or-before the current end-of-line and
873  *   the we centre that line.
874  * - We need to rewind to an unchanged section, and wind forward again
875  *   to make sure that 'lo' and 'hi' are set properly.
876  * - every time we move, we redraw the second pane (see how that goes).
877  */
878
879 /* draw_mside draws one text line or, in the case of sidebyside, one side
880  * of a textline.
881  * The 'mode' tells us what to draw via the 'visible()' function.
882  * It is one of ORIG RESULT BEFORE AFTER or ORIG|RESULT or BEFORE|AFTER
883  * It may also have WIGGLED or CONFLICTED ored in to trigger extra highlights.
884  * The desired cursor position is given in 'target' the actual end
885  * cursor position (allowing e.g. for tabs) is returned in *colp.
886  */
887 static void draw_mside(int mode, int row, int offset, int start, int cols,
888                        struct file fm, struct file fb, struct file fa,
889                        struct merge *m,
890                        struct mpos pos,
891                        struct cursor *curs)
892 {
893         struct elmnt e;
894         int col = 0;
895         char tag;
896         unsigned int tag_attr;
897         int changed = 0;
898
899         switch (pos.state) {
900         default: /* keep compiler happy */
901         case 0: /* unchanged line */
902                 tag = ' ';
903                 tag_attr = A_NORMAL;
904                 break;
905         case 1: /* 'before' text */
906                 tag = '-';
907                 tag_attr = a_delete;
908                 if ((mode & ORIG) && (mode & CONFLICTED)) {
909                         tag = '|';
910                         tag_attr = a_delete | A_REVERSE;
911                 }
912                 mode &= (ORIG|BEFORE);
913                 break;
914         case 2: /* the 'after' part */
915                 tag = '+';
916                 tag_attr = a_added;
917                 mode &= (AFTER|RESULT);
918                 break;
919         }
920
921         if (visible(mode, m, &pos) < 0) {
922                 /* Not visible, just draw a blank */
923                 blank(row, offset, cols, a_void);
924                 if (curs) {
925                         curs->width = -1;
926                         curs->col = 0;
927                         curs->pos = pos.p;
928                         curs->offset = 0;
929                 }
930                 return;
931         }
932
933         (void)attrset(tag_attr);
934         mvaddch(row, offset, tag);
935         offset++;
936         cols--;
937         (void)attrset(A_NORMAL);
938
939         if (check_line(pos, fm, fb, fa, m, mode))
940                 changed = 1;
941
942         /* find previous visible newline, or start of file */
943         do
944                 e = prev_melmnt(&pos.p, fm, fb, fa, m);
945         while (e.start != NULL &&
946                (!ends_line(e) ||
947                 visible(mode, m, &pos) == -1));
948
949         while (1) {
950                 unsigned char *c;
951                 unsigned int attr;
952                 int highlight_space;
953                 int l;
954                 e = next_melmnt(&pos.p, fm, fb, fa, m);
955                 if (!e.start)
956                         break;
957
958                 if (visible(mode, m, &pos) == -1)
959                         continue;
960                 if (e.start[0] == 0)
961                         break;
962                 c = (unsigned char *)e.start - e.prefix;
963                 highlight_space = 0;
964                 attr = visible(mode, m, &pos);
965                 if ((attr == a_unmatched || attr == a_extra) &&
966                     changed)
967                         /* Only highlight spaces if there is a tab nearby */
968                         for (l = 0; l < e.plen + e.prefix; l++)
969                                 if (c[l] == '\t')
970                                         highlight_space = 1;
971                 if (!highlight_space && (c[0] == ' ' || c[0] == '\t')) {
972                         /* always highlight space/tab at end-of-line */
973                         struct mp nxt = pos.p;
974                         struct elmnt nxte = next_melmnt(&nxt, fm, fb, fa, m);
975                         if (nxte.start[0] == '\n')
976                                 highlight_space = 1;
977                 }
978                 for (l = 0; l < e.plen + e.prefix; l++) {
979                         int scol = col;
980                         if (*c == '\n')
981                                 break;
982                         (void)attrset(attr);
983                         if (*c >= ' ' && *c != 0x7f) {
984                                 if (highlight_space)
985                                         (void)attrset(attr|A_REVERSE);
986                                 if (col >= start && col < start+cols)
987                                         mvaddch(row, col-start+offset, *c);
988                                 col++;
989                         } else if (*c == '\t') {
990                                 if (highlight_space)
991                                         (void)attrset(attr|A_UNDERLINE);
992                                 do {
993                                         if (col >= start && col < start+cols) {
994                                                 mvaddch(row, col-start+offset, ' ');
995                                         } col++;
996                                 } while ((col&7) != 0);
997                         } else {
998                                 if (col >= start && col < start+cols)
999                                         mvaddch(row, col-start+offset, '?');
1000                                 col++;
1001                         }
1002                         if (curs) {
1003                                 if (curs->target >= 0) {
1004                                         if (curs->target < col) {
1005                                                 /* Found target column */
1006                                                 curs->pos = pos.p;
1007                                                 curs->offset = l;
1008                                                 curs->col = scol;
1009                                                 if (scol >= start + cols)
1010                                                         /* Didn't appear on screen */
1011                                                         curs->width = 0;
1012                                                 else
1013                                                         curs->width = col - scol;
1014                                                 curs = NULL;
1015                                         }
1016                                 } else if (l == curs->offset &&
1017                                            same_mp(pos.p, curs->pos)) {
1018                                         /* Found the pos */
1019                                         curs->target = scol;
1020                                         curs->col = scol;
1021                                         if (scol >= start + cols)
1022                                                 /* Didn't appear on screen */
1023                                                 curs->width = 0;
1024                                         else
1025                                                 curs->width = col - scol;
1026                                         curs = NULL;
1027                                 }
1028                         }
1029                         c++;
1030                 }
1031                 if ((ends_line(e)
1032                      && visible(mode, m, &pos) != -1))
1033                         break;
1034         }
1035
1036         /* We have reached the end of visible line, or end of file */
1037         if (curs) {
1038                 curs->col = col;
1039                 if (col >= start + cols)
1040                         curs->width = 0;
1041                 else
1042                         curs->width = -1; /* end of line */
1043                 if (curs->target >= 0) {
1044                         curs->pos = pos.p;
1045                         curs->offset = 0;
1046                 } else if (same_mp(pos.p, curs->pos))
1047                         curs->target = col;
1048         }
1049         if (col < start)
1050                 col = start;
1051         if (e.start && e.start[0] == 0) {
1052                 char b[100];
1053                 struct elmnt e1;
1054                 int A, B, C, D, E, F;
1055                 e1 = fb.list[m[pos.p.m].b + pos.p.o];
1056                 sscanf(e1.start+1, "%d %d %d", &A, &B, &C);
1057                 sscanf(e.start+1, "%d %d %d", &D, &E, &F);
1058                 snprintf(b, sizeof(b), "@@ -%d,%d +%d,%d @@%s", B, C, E, F, e1.start+18);
1059                 (void)attrset(a_sep);
1060
1061                 mvaddstr(row, col-start+offset, b);
1062                 col += strlen(b);
1063         }
1064         blank(row, col-start+offset, start+cols-col,
1065               e.start
1066               ? (unsigned)visible(mode, m, &pos)
1067               : A_NORMAL);
1068 }
1069
1070 /* Draw either 1 or 2 sides depending on the mode. */
1071
1072 static void draw_mline(int mode, int row, int start, int cols,
1073                        struct file fm, struct file fb, struct file fa,
1074                        struct merge *m,
1075                        struct mpos pos,
1076                        struct cursor *curs)
1077 {
1078         /*
1079          * Draw the left and right images of this line
1080          * One side might be a_blank depending on the
1081          * visibility of this newline
1082          */
1083         int lcols, rcols;
1084
1085         mode |= check_line(pos, fm, fb, fa, m, mode);
1086
1087         if ((mode & (BEFORE|AFTER)) &&
1088             (mode & (ORIG|RESULT))) {
1089
1090                 lcols = (cols-1)/2;
1091                 rcols = cols - lcols - 1;
1092
1093                 (void)attrset(A_STANDOUT);
1094                 mvaddch(row, lcols, '|');
1095
1096                 draw_mside(mode&~(BEFORE|AFTER), row, 0, start, lcols,
1097                            fm, fb, fa, m, pos, curs && !curs->alt ? curs : NULL);
1098
1099                 draw_mside(mode&~(ORIG|RESULT), row, lcols+1, start, rcols,
1100                            fm, fb, fa, m, pos, curs && curs->alt ? curs : NULL);
1101         } else
1102                 draw_mside(mode, row, 0, start, cols,
1103                            fm, fb, fa, m, pos, curs);
1104 }
1105
1106 static char *merge_help[] = {
1107         "This view shows the merge of the patch with the",
1108         "original file.  It is like a full-context diff showing",
1109         "removed lines with a '-' prefix and added lines with a",
1110         "'+' prefix.",
1111         "In cases where a patch chunk could not be successfully",
1112         "applied, the original text is prefixed with a '|', and",
1113         "the text that the patch wanted to add is prefixed with",
1114         "a '+'.",
1115         "When the cursor is over such a conflict, or over a chunk",
1116         "which required wiggling to apply (i.e. there was unmatched",
1117         "text in the original, or extraneous unchanged text in",
1118         "the patch), the terminal is split and the bottom pane is",
1119         "use to display the part of the patch that applied to",
1120         "this section of the original.  This allows you to confirm",
1121         "that a wiggled patch applied correctly, and to see",
1122         "why there was a conflict",
1123         NULL
1124 };
1125 static char *diff_help[] = {
1126         "This is the 'diff' or 'patch' view.  It shows",
1127         "only the patch that is being applied without the",
1128         "original to which it is being applied.",
1129         "Underlined text indicates parts of the patch which",
1130         "resulted in a conflict when applied to the",
1131         "original.",
1132         NULL
1133 };
1134 static char *orig_help[] = {
1135         "This is the 'original' view which simply shows",
1136         "the original file before applying the patch.",
1137         "Sections of code that would be changed by the patch",
1138         "are highlighted in red.",
1139         NULL
1140 };
1141 static char *result_help[] = {
1142         "This is the 'result' view which shows just the",
1143         "result of applying the patch.  When a conflict",
1144         "occurred this view does not show the full conflict",
1145         "but only the 'after' part of the patch.  To see",
1146         "the full conflict, use the 'merge' or 'sidebyside'",
1147         "views.",
1148         NULL
1149 };
1150 static char *before_help[] = {
1151         "This view shows the 'before' section of a patch.",
1152         "It allows the expected match text to be seen uncluttered",
1153         "by text that is meant to replaced it.",
1154         "Red text is text that will be removed by the patch",
1155         NULL
1156 };
1157 static char *after_help[] = {
1158         "This view shows the 'after' section of a patch.",
1159         "It allows the intended result to be seen uncluttered",
1160         "by text that was meant to be matched and replaced.",
1161         "Green text is text that was added by the patch - it",
1162         "was not present in the 'before' part of the patch",
1163         NULL
1164 };
1165 static char *sidebyside_help[] = {
1166         "This is the Side By Side view of a patched file.",
1167         "The left side shows the original and the result.",
1168         "The right side shows the patch which was applied",
1169         "and lines up with the original/result as much as",
1170         "possible.",
1171         "",
1172         "Where one side has no line which matches the",
1173         "other side it is displayed as a solid colour in the",
1174         "yellow family (depending on your terminal window).",
1175         NULL
1176 };
1177 static char *merge_window_help[] = {
1178         "  Highlight Colours and Keystroke commands",
1179         "",
1180         "In all different views of a merge, highlight colours",
1181         "are used to show which parts of lines were added,",
1182         "removed, already changed, unchanged or in conflict.",
1183         "Colours and their use are:",
1184         " normal              unchanged text",
1185         " red                 text that was removed or changed",
1186         " green               text that was added or the result",
1187         "                     of a change",
1188         " yellow background   used in side-by-side for a line",
1189         "                     which has no match on the other",
1190         "                     side",
1191         " blue                text in the original which did not",
1192         "                     match anything in the patch",
1193         " cyan                text in the patch which did not",
1194         "                     match anything in the original",
1195         " cyan background     already changed text: the result",
1196         "                     of the patch matches the original",
1197         " underline           remove or added text can also be",
1198         "                     underlined indicating that it",
1199         "                     was involved in a conflict",
1200         "",
1201         "While viewing a merge various keystroke commands can",
1202         "be used to move around and change the view.  Basic",
1203         "movement commands from both 'vi' and 'emacs' are",
1204         "available:",
1205         "",
1206         " p control-p k UP    Move to previous line",
1207         " n control-n j DOWN  Move to next line",
1208         " l LEFT              Move one char to right",
1209         " h RIGHT             Move one char to left",
1210         " / control-s         Enter incremental search mode",
1211         " control-r           Enter reverse-search mode",
1212         " control-g           Search again",
1213         " ?                   Display help message",
1214         " ESC-<  0-G          Go to start of file",
1215         " ESC->  G            Go to end of file",
1216         " q                   Return to list of files or exit",
1217         " S                   Arrange for merge to be saved on exit",
1218         " control-C           Disable auto-save-on-exit",
1219         " control-L           recenter current line",
1220         " control-V SPACE     page down",
1221         " ESC-v   BACKSPC     page up",
1222         " N                   go to next patch chunk",
1223         " P                   go to previous patch chunk",
1224         " C                   go to next conflicted chunk",
1225         " C-X-o   O           move cursor to alternate pane",
1226         " ^ control-A         go to start of line",
1227         " $ control-E         go to end of line",
1228         "",
1229         " a                   display 'after' view",
1230         " b                   display 'before' view",
1231         " o                   display 'original' view",
1232         " r                   display 'result' view",
1233         " d                   display 'diff' or 'patch' view",
1234         " m                   display 'merge' view",
1235         " |                   display side-by-side view",
1236         "",
1237         " I                   toggle whether spaces are ignored",
1238         "                     when matching text.",
1239         " x                   toggle ignoring of current Changed,",
1240         "                     Conflict, or Unmatched item",
1241         " c                   toggle accepting of result of conflict",
1242         " X                   Revert 'c' and 'x' changes on this line",
1243         " v                   Save the current merge and run the",
1244         "                     default editor on the file.",
1245         NULL
1246 };
1247 static char *save_query[] = {
1248         "",
1249         "You have modified the merge.",
1250         "Would you like to save it?",
1251         " Y = save the modified merge",
1252         " N = discard modifications, don't save",
1253         " Q = return to viewing modified merge",
1254         NULL
1255 };
1256
1257 static char *toggle_ignore[] = {
1258         "",
1259         "You have modified the merge.",
1260         "Toggling ignoring of spaces will discard changes.",
1261         "Do you want to proceed?",
1262         " Y = discard changes and toggle ignoring of spaces",
1263         " N = keep changes, don't toggle",
1264         NULL
1265 };
1266
1267 static void do_edit(char *file, int line)
1268 {
1269         char *ed = getenv("VISUAL");
1270         char linebuf[20];
1271         if (!ed)
1272                 ed = getenv("EDITOR");
1273         if (!ed)
1274                 ed = "/usr/bin/edit";
1275         snprintf(linebuf, sizeof(linebuf), "+%d", line);
1276         switch(fork()) {
1277         case 0:
1278                 execlp(ed, ed, linebuf, file, NULL);
1279                 exit(2);
1280         case -1:
1281                 break;
1282         default:
1283                 wait(NULL);
1284         }
1285 }
1286
1287 static void *memdup(void *a, int len)
1288 {
1289         char *r = malloc(len);
1290         memcpy(r, a, len);
1291         return r;
1292 }
1293
1294 static int merge_window(struct plist *p, FILE *f, int reverse, int replace,
1295                         int selftest, int ignore_blanks, int just_diff, int backup)
1296 {
1297         /* Display the merge window in one of the selectable modes,
1298          * starting with the 'merge' mode.
1299          *
1300          * Newlines are the key to display.
1301          * 'pos' is always a visible newline (or eof).
1302          * In sidebyside mode it might only be visible on one side,
1303          * in which case the other side will be blank.
1304          * Where the newline is visible, we rewind the previous visible
1305          * newline visible and display the stuff in between
1306          *
1307          * A 'position' is a struct mpos
1308          */
1309
1310         struct stream sm, sb, sa, sp; /* main, before, after, patch */
1311         struct file fm, fb, fa;
1312         struct csl *csl1, *csl2;
1313         struct ci ci;
1314         int ch; /* count of chunks */
1315         /* Always refresh the current line.
1316          * If refresh == 1, refresh all lines.  If == 2, clear first
1317          */
1318         int refresh = 2;
1319         int rows = 0, cols = 0;
1320         int splitrow = -1; /* screen row for split - diff appears below */
1321         int lastrow = 0; /* end of screen, or just above 'splitrow' */
1322         int i, c, cswitch;
1323         MEVENT mevent;
1324         int mode = just_diff ? (BEFORE|AFTER) : (ORIG|RESULT);
1325         int mmode = mode; /* Mode for moving - used when in 'other' pane */
1326         char *modename = just_diff ? "diff" : "merge";
1327         char **modehelp = just_diff ? diff_help : merge_help;
1328         char *mesg = NULL;
1329
1330         int row, start = 0;
1331         int trow; /* screen-row while searching.  If we cannot find,
1332                    * we forget this number */
1333         struct cursor curs;
1334         struct mpos pos;  /* current point */
1335         struct mpos tpos, /* temp point while drawing lines above and below pos */
1336                 toppos,   /* pos at top of screen - for page-up */
1337                 botpos;   /* pos at bottom of screen - for page-down */
1338         struct mpos vispos;
1339         int botrow = 0;
1340         int meta = 0,     /* mode for multi-key commands- SEARCH or META */
1341                 tmeta;
1342         int num = -1,     /* numeric arg being typed. */
1343                 tnum;
1344         int lineno;
1345         int changes = 0; /* If any edits have been made to the merge */
1346         int answer;     /* answer to 'save changes?' question */
1347         char *tempname;
1348         struct elmnt e;
1349         char search[80];  /* string we are searching for */
1350         unsigned int searchlen = 0;
1351         int search_notfound = 0;
1352         int searchdir = 0;
1353         /* ignore_case:
1354          *  0 == no
1355          *  1 == no because there are upper-case chars
1356          *  2 == yes as there are no upper-case chars
1357          *  3 == yes
1358          */
1359         int ignore_case = 2;
1360         /* We record all the places we find so 'backspace'
1361          * can easily return to the previous one
1362          */
1363         struct search_anchor {
1364                 struct search_anchor *next;
1365                 struct mpos pos;
1366                 struct cursor curs;
1367                 int notfound;
1368                 int row, start;
1369                 unsigned int searchlen;
1370         } *anchor = NULL;
1371
1372         #define free_stuff(none) \
1373         do { \
1374                 free(fm.list); \
1375                 free(fb.list); \
1376                 free(fa.list); \
1377                 free(csl1); \
1378                 free(csl2); \
1379                 free(ci.merger); \
1380         } while(0)
1381
1382         #define find_line(ln) \
1383         do { \
1384                 pos.p.m = 0; /* merge node */ \
1385                 pos.p.s = 0; /* stream number */ \
1386                 pos.p.o = -1; /* offset */ \
1387                 pos.p.lineno = 1; \
1388                 pos.state = 0; \
1389                 memset(&curs, 0, sizeof(curs)); \
1390                 do \
1391                         next_mline(&pos, fm, fb, fa, ci.merger, mode); \
1392                 while (pos.p.lineno < ln && ci.merger[pos.p.m].type != End); \
1393         } while(0)
1394
1395         #define prepare_merge(ch) \
1396         do { \
1397                 /* FIXME check for errors in the stream */ \
1398                 fm = split_stream(sm, ByWord | ignore_blanks); \
1399                 fb = split_stream(sb, ByWord | ignore_blanks); \
1400                 fa = split_stream(sa, ByWord | ignore_blanks); \
1401 \
1402                 if (ch && !just_diff) \
1403                         csl1 = pdiff(fm, fb, ch); \
1404                 else \
1405                         csl1 = diff(fm, fb, 1); \
1406                 csl2 = diff_patch(fb, fa, 1); \
1407 \
1408                 ci = make_merger(fm, fb, fa, csl1, csl2, 0, 1, 0); \
1409                 for (i = 0; ci.merger[i].type != End; i++) \
1410                         ci.merger[i].oldtype = ci.merger[i].type; \
1411         } while(0)
1412
1413         if (selftest) {
1414                 intr_kills = 1;
1415                 selftest = 1;
1416         }
1417
1418         if (f == NULL) {
1419                 if (!p->is_merge) {
1420                         /* three separate files */
1421                         sb = load_file(p->before);
1422                         sa = load_file(p->after);
1423                         if (just_diff)
1424                                 sm = sb;
1425                         else
1426                                 sm = load_file(p->file);
1427                 } else {
1428                         /* One merge file */
1429                         sp = load_file(p->file);
1430                         if (reverse)
1431                                 split_merge(sp, &sm, &sa, &sb);
1432                         else
1433                                 split_merge(sp, &sm, &sb, &sa);
1434                         free(sp.body);
1435                 }
1436                 ch = 0;
1437         } else {
1438                 sp = load_segment(f, p->start, p->end);
1439                 if (p->is_merge) {
1440                         if (reverse)
1441                                 split_merge(sp, &sm, &sa, &sb);
1442                         else
1443                                 split_merge(sp, &sm, &sb, &sa);
1444                         ch = 0;
1445                 } else {
1446                         if (reverse)
1447                                 ch = split_patch(sp, &sa, &sb);
1448                         else
1449                                 ch = split_patch(sp, &sb, &sa);
1450                         if (just_diff)
1451                                 sm = sb;
1452                         else
1453                                 sm = load_file(p->file);
1454                 }
1455                 free(sp.body);
1456         }
1457         if (!sm.body || !sb.body || !sa.body) {
1458                 if (!just_diff)
1459                         free(sm.body);
1460                 free(sb.body);
1461                 free(sa.body);
1462                 term_init(1);
1463                 if (!sm.body)
1464                         help_window(help_missing, NULL, 0);
1465                 else
1466                         help_window(help_corrupt, NULL, 0);
1467                 endwin();
1468                 return 0;
1469         }
1470         prepare_merge(ch);
1471         term_init(!selftest);
1472
1473         row = 1;
1474         find_line(1);
1475
1476         while (1) {
1477                 unsigned int next;
1478                 if (refresh >= 2) {
1479                         clear();
1480                         refresh = 1;
1481                 }
1482                 if (row < 1 || row >= lastrow)
1483                         refresh = 1;
1484                 if (curs.alt)
1485                         refresh = 1;
1486
1487                 if (mode == (ORIG|RESULT)) {
1488                         int cmode = check_line(pos, fm, fb, fa, ci.merger, mode);
1489                         if (cmode & (WIGGLED | CONFLICTED)) {
1490                                 if (splitrow < 0) {
1491                                         splitrow = (rows+1)/2;
1492                                         lastrow = splitrow - 1;
1493                                         refresh = 1;
1494                                 }
1495                         } else if (!curs.alt && splitrow >= 0) {
1496                                 splitrow = -1;
1497                                 lastrow = rows-1;
1498                                 refresh = 1;
1499                         }
1500                 } else if (splitrow >= 0) {
1501                         splitrow = -1;
1502                         lastrow = rows-1;
1503                         refresh = 1;
1504                 }
1505
1506                 if (refresh) {
1507                         getmaxyx(stdscr, rows, cols);
1508                         rows--; /* keep last row clear */
1509                         if (splitrow >= 0) {
1510                                 splitrow = (rows+1)/2;
1511                                 lastrow = splitrow - 1;
1512                         } else
1513                                 lastrow =  rows - 1;
1514
1515                         if (row < -3)
1516                                 row = lastrow/2+1;
1517                         if (row < 1)
1518                                 row = 1;
1519                         if (row > lastrow+3)
1520                                 row = lastrow/2+1;
1521                         if (row >= lastrow)
1522                                 row = lastrow-1;
1523                 }
1524
1525                 /* Always refresh the line */
1526                 while (start > curs.target) {
1527                         start -= 8;
1528                         refresh = 1;
1529                 }
1530                 if (start < 0)
1531                         start = 0;
1532                 vispos = pos; /* visible position - if cursor is in
1533                                * alternate pane, pos might not be visible
1534                                * in main pane. */
1535                 if (check_line(vispos, fm, fb, fa, ci.merger, mode)
1536                     & CHANGES) {
1537                         if (vispos.state == 0) {
1538                                 vispos.state = 1;
1539                                 vispos.lo = vispos.p;
1540                                 vispos.hi = vispos.p;
1541                         }
1542                 } else {
1543                         vispos.state = 0;
1544                 }
1545
1546                 if (visible(mode, ci.merger, &vispos) < 0)
1547                         prev_mline(&vispos, fm, fb, fa, ci.merger, mode);
1548                 if (!curs.alt)
1549                         pos= vispos;
1550         retry:
1551                 draw_mline(mode, row, start, cols, fm, fb, fa, ci.merger,
1552                            vispos, (splitrow >= 0 && curs.alt) ? NULL : &curs);
1553                 if (curs.width == 0 && start < curs.col) {
1554                         /* width == 0 implies it appear after end-of-screen */
1555                         start += 8;
1556                         refresh = 1;
1557                         goto retry;
1558                 }
1559                 if (curs.col < start) {
1560                         start -= 8;
1561                         refresh = 1;
1562                         if (start < 0)
1563                                 start = 0;
1564                         goto retry;
1565                 }
1566                 if (refresh) {
1567                         refresh = 0;
1568
1569                         tpos = vispos;
1570
1571                         for (i = row-1; i >= 1 && tpos.p.m >= 0; ) {
1572                                 prev_mline(&tpos, fm, fb, fa, ci.merger, mode);
1573                                 draw_mline(mode, i--, start, cols,
1574                                            fm, fb, fa, ci.merger,
1575                                            tpos, NULL);
1576
1577                         }
1578                         if (i > 0) {
1579                                 row -= (i+1);
1580                                 refresh = 1;
1581                                 goto retry;
1582                         }
1583                         toppos = tpos;
1584                         while (i >= 1)
1585                                 blank(i--, 0, cols, a_void);
1586                         tpos = vispos;
1587                         for (i = row; i <= lastrow && ci.merger[tpos.p.m].type != End; ) {
1588                                 draw_mline(mode, i++, start, cols,
1589                                            fm, fb, fa, ci.merger,
1590                                            tpos, NULL);
1591                                 next_mline(&tpos, fm, fb, fa, ci.merger, mode);
1592                         }
1593                         botpos = tpos; botrow = i;
1594                         while (i <= lastrow)
1595                                 blank(i++, 0, cols, a_void);
1596                 }
1597
1598                 if (splitrow >= 0) {
1599                         struct mpos spos = pos;
1600                         int smode = BEFORE|AFTER;
1601                         int srow = (rows + splitrow)/2;
1602                         if (check_line(spos, fm, fb, fa, ci.merger, smode)
1603                             & CHANGES) {
1604                                 if (spos.state == 0)
1605                                         spos.state = 1;
1606                         } else {
1607                                 spos.state = 0;
1608                         }
1609                         if (visible(smode, ci.merger, &spos) < 0)
1610                                 prev_mline(&spos, fm, fb, fa, ci.merger, smode);
1611                         /* Now hi/lo might be wrong, so lets fix it. */
1612                         tpos = spos;
1613                         if (spos.state)
1614                                 /* 'hi' might be wrong so we mustn't depend
1615                                  * on it while walking back.  So set state
1616                                  * to 1 to avoid ever testing it.
1617                                  */
1618                                 spos.state = 1;
1619                         while (spos.p.m >= 0 && spos.state != 0)
1620                                 prev_mline(&spos, fm, fb, fa, ci.merger, smode);
1621                         while (!same_mpos(spos, tpos) &&
1622                                spos.p.m >= 0 &&
1623                                ci.merger[spos.p.m].type != End)
1624                                 next_mline(&spos, fm, fb, fa, ci.merger, smode);
1625
1626                         (void)attrset(a_sep);
1627                         for (i = 0; i < cols; i++)
1628                                 mvaddstr(splitrow, i, "-");
1629
1630                         tpos = spos;
1631                         for (i = srow-1; i > splitrow; i--) {
1632                                 prev_mline(&tpos, fm, fb, fa, ci.merger, smode);
1633                                 draw_mline(smode, i, start, cols, fm, fb, fa, ci.merger,
1634                                            tpos, NULL);
1635                         }
1636                         while (i > splitrow)
1637                                 blank(i--, 0, cols, a_void);
1638                         tpos = spos;
1639                         for (i = srow;
1640                              i < rows && tpos.p.m >= 0 &&
1641                              ci.merger[tpos.p.m].type != End;
1642                              i++) {
1643                                 draw_mline(smode, i, start, cols, fm, fb, fa, ci.merger,
1644                                            tpos,
1645                                            (i == srow && curs.alt) ? &curs : NULL);
1646                                 next_mline(&tpos, fm, fb, fa, ci.merger, smode);
1647                         }
1648                         while (i < rows)
1649                                 blank(i++, 0, cols, a_void);
1650                 }
1651                 /* Now that curs is accurate, report the type */
1652                 {
1653                         int l = 0;
1654                         char buf[100];
1655                         if (p->file)
1656                                 l = snprintf(buf, 100, "File: %s%s ",
1657                                 p->file, reverse ? " - reversed" : "");
1658                         snprintf(buf+l, 100-l, "Mode: %s", modename);
1659                         (void)attrset(A_BOLD);
1660                         mvaddstr(0, 0, buf);
1661                         (void)attrset(A_NORMAL);
1662                         if (ignore_blanks)
1663                                 addstr(" (ignoring blanks)");
1664                         clrtoeol();
1665                         (void)attrset(A_BOLD);
1666                         if (ci.merger[curs.pos.m].type != ci.merger[curs.pos.m].oldtype)
1667                                 l = snprintf(buf, sizeof(buf), "%s->", typenames[ci.merger[curs.pos.m].oldtype]);
1668                         snprintf(buf+l, sizeof(buf)-l, "%s ln:%d",
1669                                  typenames[ci.merger[curs.pos.m].type],
1670                                  (pos.p.lineno-1)/2);
1671                         mvaddstr(0, cols - strlen(buf) - 1, buf);
1672                 }
1673 #define META(c) ((c)|0x1000)
1674 #define SEARCH(c) ((c)|0x2000)
1675 #define CTRLX(c) ((c)|0x4000)
1676                 move(rows, 0);
1677                 (void)attrset(A_NORMAL);
1678                 if (mesg) {
1679                         attrset(A_REVERSE);
1680                         addstr(mesg);
1681                         mesg = NULL;
1682                         attrset(A_NORMAL);
1683                 }
1684                 if (num >= 0) {
1685                         char buf[12+1];
1686                         snprintf(buf, sizeof(buf), "%d ", num);
1687                         addstr(buf);
1688                 }
1689                 if (meta & META(0))
1690                         addstr("ESC...");
1691                 if (meta & CTRLX(0))
1692                         addstr("C-x ");
1693                 if (meta & SEARCH(0)) {
1694                         if (searchdir < 0)
1695                                 addstr("Backwards ");
1696                         addstr("Search: ");
1697                         addstr(search);
1698                         if (search_notfound)
1699                                 addstr(" - Not Found.");
1700                         search_notfound = 0;
1701                 }
1702                 clrtoeol();
1703                 /* '+1' to skip over the leading +/-/| char */
1704                 if (curs.alt && splitrow > 0)
1705                         move((rows + splitrow)/2, curs.col - start + 1);
1706                 else if (curs.alt && ((mode & (BEFORE|AFTER)) &&
1707                                       (mode & (ORIG|RESULT))))
1708                         move(row, curs.col-start + (cols-1)/2+2);
1709                 else
1710                         move(row, curs.col-start+1);
1711                 switch (selftest) {
1712                 case 0:
1713                         c = getch(); break;
1714                 case 1:
1715                         c = 'n'; break;
1716                 case 2:
1717                         c = 'q'; break;
1718                 }
1719                 tmeta = meta; meta = 0;
1720                 tnum = num; num = -1;
1721                 cswitch = c | tmeta;
1722                 /* Handle some ranges */
1723                 /* case '0' ... '9': */
1724                 if (cswitch >= '0' && cswitch <= '9')
1725                         cswitch = '0';
1726                 /* case SEARCH(' ') ... SEARCH('~'): */
1727                 if (cswitch >= SEARCH(' ') && cswitch <= SEARCH('~'))
1728                         cswitch = SEARCH(' ');
1729
1730                 switch (cswitch) {
1731                 case 27: /* escape */
1732                 case META(27):
1733                         meta = META(0);
1734                         break;
1735
1736                 case 'X'-64:
1737                 case META('X'-64):
1738                         meta = CTRLX(0);
1739                         break;
1740
1741                 case META('<'): /* start of file */
1742                 start:
1743                         tpos = pos; row++;
1744                         do {
1745                                 pos = tpos; row--;
1746                                 prev_mline(&tpos, fm, fb, fa, ci.merger, mmode);
1747                         } while (tpos.p.m >= 0);
1748                         if (row <= 0)
1749                                 row = 0;
1750                         break;
1751                 case META('>'): /* end of file */
1752                 case 'G':
1753                         if (tnum >= 0)
1754                                 goto start;
1755                         tpos = pos; row--;
1756                         do {
1757                                 pos = tpos; row++;
1758                                 next_mline(&tpos, fm, fb, fa, ci.merger, mmode);
1759                         } while (ci.merger[tpos.p.m].type != End);
1760                         if (row >= lastrow)
1761                                 row = lastrow;
1762                         break;
1763                 case '0': /* actually '0'...'9' */
1764                         if (tnum < 0)
1765                                 tnum = 0;
1766                         num = tnum*10 + (c-'0');
1767                         break;
1768                 case 'C'-64:
1769                         if (replace)
1770                                 mesg = "Autosave disabled";
1771                         else
1772                                 mesg = "Use 'q' to quit";
1773                         replace = 0;
1774                         break;
1775                 case 'S':
1776                         mesg = "Will auto-save on exit, using Ctrl-C to cancel";
1777                         replace = 1;
1778                         break;
1779                 case 'q':
1780                         refresh = 2;
1781                         answer = 0;
1782                         if (replace)
1783                                 answer = 1;
1784                         else if (changes)
1785                                 answer = help_window(save_query, NULL, 1);
1786                         if (answer < 0)
1787                                 break;
1788                         if (answer) {
1789                                 p->wiggles = 0;
1790                                 p->conflicts = isolate_conflicts(
1791                                         fm, fb, fa, csl1, csl2, 0,
1792                                         ci.merger, 0, &p->wiggles);
1793                                 p->chunks = p->conflicts;
1794                                 save_merge(fm, fb, fa, ci.merger,
1795                                            p->outfile ? p->outfile : p->file,
1796                                            backup && (p->outfile ? 0 : !p->is_merge));
1797                         }
1798                         if (!just_diff)
1799                                 free(sm.body);
1800                         free(sb.body);
1801                         free(sa.body);
1802                         free_stuff();
1803                         endwin();
1804                         return answer;
1805
1806                 case 'I': /* Toggle ignoring of spaces */
1807                         if (changes) {
1808                                 refresh = 2;
1809                                 answer = help_window(toggle_ignore, NULL, 1);
1810                                 if (answer <= 0)
1811                                         break;
1812                                 changes = 0;
1813                         }
1814                         free_stuff();
1815                         ignore_blanks = ignore_blanks ? 0 : IgnoreBlanks;
1816                         prepare_merge(ch);
1817                         find_line(pos.p.lineno);
1818
1819                         refresh = 2;
1820                         break;
1821
1822                 case 'v':
1823                         if (!p->file || just_diff) {
1824                                 mesg = "Cannot run editor when diffing";
1825                                 break;
1826                         }
1827                         tempname = p->file;
1828                         lineno = save_tmp_merge(fm, fb, fa, ci.merger,
1829                                                 &tempname,
1830                                                 ci.merger + pos.p.m,
1831                                                 pos.p.s,
1832                                                 pos.p.o);
1833                         endwin();
1834                         free_stuff();
1835                         do_edit(tempname, lineno);
1836                         sp = load_file(tempname);
1837                         unlink(tempname);
1838                         split_merge(sp, &sm, &sb, &sa);
1839                         if (sp.len == sm.len &&
1840                             memcmp(sp.body, sm.body, sm.len) == 0 &&
1841                                 !p->is_merge) {
1842                                 /* no conflicts left, so display diff */
1843                                 free(sm.body);
1844                                 sm = load_file(p->file);
1845                                 free(sb.body);
1846                                 sb = sm;
1847                                 sb.body = memdup(sm.body, sm.len);
1848                         }
1849                         free(sp.body);
1850                         ignore_blanks = 0;
1851                         prepare_merge(0);
1852                         refresh = 2;
1853                         changes = 1;
1854
1855                         find_line(pos.p.lineno);
1856
1857                         doupdate();
1858                         break;
1859
1860                 case '/':
1861                 case 'S'-64:
1862                         /* incr search forward */
1863                         meta = SEARCH(0);
1864                         searchlen = 0;
1865                         search[searchlen] = 0;
1866                         searchdir = 1;
1867                         break;
1868                 case '\\':
1869                 case 'R'-64:
1870                         /* incr search backwards */
1871                         meta = SEARCH(0);
1872                         searchlen = 0;
1873                         search[searchlen] = 0;
1874                         searchdir = -1;
1875                         break;
1876                 case SEARCH('G'-64):
1877                 case SEARCH('S'-64):
1878                 case SEARCH('R'-64):
1879                         /* search again */
1880                         if ((c|tmeta) == SEARCH('R'-64))
1881                                 searchdir = -2;
1882                         else
1883                                 searchdir = 2;
1884                         meta = SEARCH(0);
1885                         tpos = pos; trow = row;
1886                         goto search_again;
1887
1888                 case SEARCH('H'-64):
1889                 case SEARCH(KEY_BACKSPACE):
1890                         meta = SEARCH(0);
1891                         if (anchor) {
1892                                 struct search_anchor *a;
1893                                 a = anchor;
1894                                 anchor = a->next;
1895                                 free(a);
1896                         }
1897                         if (anchor) {
1898                                 struct search_anchor *a;
1899                                 a = anchor;
1900                                 anchor = a->next;
1901                                 pos = a->pos;
1902                                 row = a->row;
1903                                 start = a->start;
1904                                 curs = a->curs;
1905                                 curs.target = -1;
1906                                 search_notfound = a->notfound;
1907                                 searchlen = a->searchlen;
1908                                 search[searchlen] = 0;
1909                                 free(a);
1910                                 refresh = 1;
1911                         }
1912                         break;
1913                 case SEARCH(' '): /* actually ' '...'~' */
1914                 case SEARCH('\t'):
1915                         meta = SEARCH(0);
1916                         if (searchlen < sizeof(search)-1)
1917                                 search[searchlen++] = c & (0x7f);
1918                         search[searchlen] = 0;
1919                         tpos = pos; trow = row;
1920                 search_again:
1921                         search_notfound = 1;
1922                         if (ignore_case == 1 || ignore_case == 2) {
1923                                 unsigned int i;
1924                                 ignore_case = 2;
1925                                 for (i=0; i < searchlen; i++)
1926                                         if (isupper(search[i])) {
1927                                                 ignore_case = 1;
1928                                                 break;
1929                                         }
1930                         }
1931                         do {
1932                                 if (mcontains(tpos, fm, fb, fa, ci.merger,
1933                                               mmode, search, &curs, searchdir,
1934                                               ignore_case >= 2)) {
1935                                         curs.target = -1;
1936                                         pos = tpos;
1937                                         row = trow;
1938                                         search_notfound = 0;
1939                                         break;
1940                                 }
1941                                 if (searchdir < 0) {
1942                                         trow--;
1943                                         prev_mline(&tpos, fm, fb, fa, ci.merger, mmode);
1944                                 } else {
1945                                         trow++;
1946                                         next_mline(&tpos, fm, fb, fa, ci.merger, mmode);
1947                                 }
1948                         } while (tpos.p.m >= 0 && ci.merger[tpos.p.m].type != End);
1949                         searchdir /= abs(searchdir);
1950
1951                         break;
1952                 case 'L'-64:
1953                         refresh = 2;
1954                         row = lastrow / 2;
1955                         break;
1956
1957                 case KEY_NPAGE:
1958                 case ' ':
1959                 case 'V'-64: /* page down */
1960                         pos = botpos;
1961                         if (botrow <= lastrow) {
1962                                 row = botrow;
1963                                 if (selftest == 1)
1964                                         selftest = 2;
1965                         } else
1966                                 row = 2;
1967                         refresh = 1;
1968                         break;
1969                 case KEY_PPAGE:
1970                 case KEY_BACKSPACE:
1971                 case META('v'): /* page up */
1972                         pos = toppos;
1973                         row = lastrow-1;
1974                         refresh = 1;
1975                         break;
1976
1977                 case KEY_MOUSE:
1978                         if (getmouse(&mevent) != OK)
1979                                 break;
1980                         /* First see if this is on the 'other' pane */
1981                         if (splitrow > 0) {
1982                                 /* merge mode, top and bottom */
1983                                 if ((curs.alt && mevent.y < splitrow) ||
1984                                     (!curs.alt && mevent.y > splitrow)) {
1985                                         goto other_pane;
1986                                 }
1987                         } else if (mode == (ORIG|RESULT|BEFORE|AFTER)) {
1988                                 /* side-by-side mode */
1989                                 if ((curs.alt && mevent.x < cols/2) ||
1990                                     (!curs.alt && mevent.x > cols/2)) {
1991                                         goto other_pane;
1992                                 }
1993                         }
1994                         /* Now try to find the right line */
1995                         if (splitrow < 0 || !curs.alt)
1996                                 trow = row;
1997                         else
1998                                 trow = (rows + splitrow)/2;
1999                         while (trow > mevent.y) {
2000                                 tpos = pos;
2001                                 prev_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2002                                 if (tpos.p.m >= 0) {
2003                                         pos = tpos;
2004                                         trow--;
2005                                 } else
2006                                         break;
2007                         }
2008                         while (trow < mevent.y) {
2009                                 tpos = pos;
2010                                 next_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2011                                 if (ci.merger[tpos.p.m].type != End) {
2012                                         pos = tpos;
2013                                         trow++;
2014                                 } else
2015                                         break;
2016                         }
2017                         if (splitrow < 0 || !curs.alt)
2018                                 /* it is OK to change the row */
2019                                 row = trow;
2020
2021                         /* Now set the target column */
2022                         if (mode == (ORIG|RESULT|BEFORE|AFTER) &&
2023                             curs.alt)
2024                                 curs.target = start + mevent.x - cols / 2 - 1;
2025                         else
2026                                 curs.target = start + mevent.x - 1;
2027                         break;
2028                 case 'j':
2029                 case 'n':
2030                 case 'N'-64:
2031                 case KEY_DOWN:
2032                         if (tnum < 0)
2033                                 tnum = 1;
2034                         for (; tnum > 0 ; tnum--) {
2035                                 tpos = pos;
2036                                 next_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2037                                 if (ci.merger[tpos.p.m].type != End) {
2038                                         pos = tpos;
2039                                         row++;
2040                                 } else {
2041                                         if (selftest == 1)
2042                                                 selftest = 2;
2043                                         break;
2044                                 }
2045                         }
2046                         break;
2047                 case 'N':
2048                         /* Next diff */
2049                         tpos = pos; row--;
2050                         do {
2051                                 pos = tpos; row++;
2052                                 next_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2053                         } while (!(pos.state == 0
2054                                    && (check_line(pos, fm, fb, fa, ci.merger, mmode)
2055                                        & (CONFLICTED|WIGGLED)) == 0)
2056                                  && ci.merger[tpos.p.m].type != End);
2057                         tpos = pos; row--;
2058                         do {
2059                                 pos = tpos; row++;
2060                                 next_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2061                         } while (pos.state == 0
2062                                  && (check_line(pos, fm, fb, fa, ci.merger, mmode)
2063                                      & (CONFLICTED|WIGGLED)) == 0
2064                                  && ci.merger[tpos.p.m].type != End);
2065
2066                         break;
2067                 case 'C':
2068                         /* Next conflict */
2069                         tpos = pos; row--;
2070                         do {
2071                                 pos = tpos; row++;
2072                                 next_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2073                         } while ((check_line(pos, fm, fb, fa, ci.merger, mmode)
2074                                   & CONFLICTED) != 0
2075                                  && ci.merger[tpos.p.m].type != End);
2076                         tpos = pos; row--;
2077                         do {
2078                                 pos = tpos; row++;
2079                                 next_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2080                         } while ((check_line(pos, fm, fb, fa, ci.merger, mmode)
2081                                   & CONFLICTED) == 0
2082                                  && ci.merger[tpos.p.m].type != End);
2083
2084                         break;
2085
2086                 case 'P':
2087                         /* Previous diff */
2088                         tpos = pos; row++;
2089                         do {
2090                                 pos = tpos; row--;
2091                                 prev_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2092                         } while (tpos.state == 0
2093                                  && (check_line(tpos, fm, fb, fa, ci.merger, mmode)
2094                                      & (CONFLICTED|WIGGLED)) == 0
2095                                  && tpos.p.m >= 0);
2096                         tpos = pos; row++;
2097                         do {
2098                                 pos = tpos; row--;
2099                                 prev_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2100                         } while (!(tpos.state == 0
2101                                    && (check_line(tpos, fm, fb, fa, ci.merger, mmode)
2102                                        & (CONFLICTED|WIGGLED)) == 0)
2103                                  && tpos.p.m >= 0);
2104                         break;
2105
2106                 case 'k':
2107                 case 'p':
2108                 case 'P'-64:
2109                 case KEY_UP:
2110                         if (tnum < 0)
2111                                 tnum = 1;
2112                         for (; tnum > 0 ; tnum--) {
2113                                 tpos = pos;
2114                                 prev_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2115                                 if (tpos.p.m >= 0) {
2116                                         pos = tpos;
2117                                         row--;
2118                                 } else
2119                                         break;
2120                         }
2121                         break;
2122
2123                 case KEY_LEFT:
2124                 case 'h':
2125                         /* left */
2126                         curs.target = curs.col - 1;
2127                         if (curs.target < 0) {
2128                                 /* Try to go to end of previous line */
2129                                 tpos = pos;
2130                                 prev_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2131                                 if (tpos.p.m >= 0) {
2132                                         pos = tpos;
2133                                         row--;
2134                                         curs.pos = pos.p;
2135                                         curs.target = -1;
2136                                 } else
2137                                         curs.target = 0;
2138                         }
2139                         break;
2140                 case KEY_RIGHT:
2141                 case 'l':
2142                         /* right */
2143                         if (curs.width >= 0)
2144                                 curs.target = curs.col + curs.width;
2145                         else {
2146                                 /* end of line, go to next */
2147                                 tpos = pos;
2148                                 next_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2149                                 if (ci.merger[tpos.p.m].type != End) {
2150                                         pos = tpos;
2151                                         curs.pos = pos.p;
2152                                         row++;
2153                                         curs.target = 0;
2154                                 }
2155                         }
2156                         break;
2157
2158                 case '^':
2159                 case 'A'-64:
2160                         /* Start of line */
2161                         curs.target = 0;
2162                         break;
2163                 case '$':
2164                 case 'E'-64:
2165                         /* End of line */
2166                         curs.target = 1000;
2167                         break;
2168
2169                 case CTRLX('o'):
2170                 case 'O':
2171                 other_pane:
2172                         curs.alt = !curs.alt;
2173                         if (curs.alt && mode == (ORIG|RESULT))
2174                                 mmode = (BEFORE|AFTER);
2175                         else
2176                                 mmode = mode;
2177                         break;
2178
2179                 case 'a': /* 'after' view in patch window */
2180                         if (mode == AFTER)
2181                                 goto set_merge;
2182                         mode = AFTER; modename = "after"; modehelp = after_help;
2183                         mmode = mode; curs.alt = 0;
2184                         refresh = 3;
2185                         break;
2186                 case 'b': /* 'before' view in patch window */
2187                         if (mode == BEFORE)
2188                                 goto set_merge;
2189                         mode = BEFORE; modename = "before"; modehelp = before_help;
2190                         mmode = mode; curs.alt = 0;
2191                         refresh = 3;
2192                         break;
2193                 case 'o': /* 'original' view in the merge window */
2194                         if (mode == ORIG)
2195                                 goto set_merge;
2196                         mode = ORIG; modename = "original"; modehelp = orig_help;
2197                         mmode = mode; curs.alt = 0;
2198                         refresh = 3;
2199                         break;
2200                 case 'r': /* the 'result' view in the merge window */
2201                         if (mode == RESULT)
2202                                 goto set_merge;
2203                         mode = RESULT; modename = "result"; modehelp = result_help;
2204                         mmode = mode; curs.alt = 0;
2205                         refresh = 3;
2206                         break;
2207                 case 'd':
2208                         if (mode == (BEFORE|AFTER))
2209                                 goto set_merge;
2210                         mode = BEFORE|AFTER; modename = "diff"; modehelp = diff_help;
2211                         mmode = mode; curs.alt = 0;
2212                         refresh = 3;
2213                         break;
2214                 case 'm':
2215                 set_merge:
2216                         mode = ORIG|RESULT; modename = "merge"; modehelp = merge_help;
2217                         mmode = mode; curs.alt = 0;
2218                         refresh = 3;
2219                         break;
2220
2221                 case '|':
2222                         if (mode == (ORIG|RESULT|BEFORE|AFTER))
2223                                 goto set_merge;
2224                         mode = ORIG|RESULT|BEFORE|AFTER; modename = "sidebyside"; modehelp = sidebyside_help;
2225                         mmode = mode; curs.alt = 0;
2226                         refresh = 3;
2227                         break;
2228
2229                 case 'H': /* scroll window to the right */
2230                         if (start > 0)
2231                                 start--;
2232                         curs.target = start + 1;
2233                         refresh = 1;
2234                         break;
2235                 case 'L': /* scroll window to the left */
2236                         if (start < cols)
2237                                 start++;
2238                         curs.target = start + 1;
2239                         refresh = 1;
2240                         break;
2241
2242                 case 'x': /* Toggle rejecting of conflict.
2243                            * A 'Conflict' or 'Changed' becomes 'Unchanged'
2244                            * 'Unmatched' becomes 'Changed'
2245                            */
2246                         if (ci.merger[curs.pos.m].oldtype == Conflict ||
2247                             ci.merger[curs.pos.m].oldtype == Changed)
2248                                 next = Unchanged;
2249                         else if (ci.merger[curs.pos.m].oldtype == Unmatched)
2250                                 next = Changed;
2251                         else
2252                                 break;
2253
2254                         if (ci.merger[curs.pos.m].type == next)
2255                                 ci.merger[curs.pos.m].type = ci.merger[curs.pos.m].oldtype;
2256                         else
2257                                 ci.merger[curs.pos.m].type = next;
2258                         p->conflicts = isolate_conflicts(
2259                                 fm, fb, fa, csl1, csl2, 0,
2260                                 ci.merger, 0, &p->wiggles);
2261                         refresh = 1;
2262                         changes = 1;
2263                         break;
2264
2265                 case 'c': /* Toggle accepting of conflict.
2266                            * A 'Conflict' or 'Extraneous' becomes 'Changed'
2267                            */
2268                         if (ci.merger[curs.pos.m].oldtype != Conflict &&
2269                             ci.merger[curs.pos.m].oldtype != Extraneous)
2270                                 break;
2271
2272                         if (ci.merger[curs.pos.m].type == Changed)
2273                                 ci.merger[curs.pos.m].type = ci.merger[curs.pos.m].oldtype;
2274                         else
2275                                 ci.merger[curs.pos.m].type = Changed;
2276                         p->conflicts = isolate_conflicts(
2277                                 fm, fb, fa, csl1, csl2, 0,
2278                                 ci.merger, 0, &p->wiggles);
2279                         refresh = 1;
2280                         changes = 1;
2281                         break;
2282
2283                 case 'X': /* Reset all changes on the current line */
2284                         tpos = pos;
2285                         do {
2286                                 ci.merger[tpos.p.m].type =
2287                                         ci.merger[tpos.p.m].oldtype;
2288                                 e = prev_melmnt(&tpos.p, fm, fb, fa, ci.merger);
2289                                 if (tpos.p.m < 0)
2290                                         break;
2291                         } while (!ends_line(e) ||
2292                                  visible(mode & (RESULT|AFTER), ci.merger, &tpos) < 0);
2293                         p->conflicts = isolate_conflicts(
2294                                 fm, fb, fa, csl1, csl2, 0,
2295                                 ci.merger, 0, &p->wiggles);
2296                         refresh = 1;
2297                         changes = 1;
2298                         break;
2299
2300                 case '?':
2301                         help_window(modehelp, merge_window_help, 0);
2302                         refresh = 2;
2303                         break;
2304
2305                 case KEY_RESIZE:
2306                         refresh = 2;
2307                         break;
2308                 }
2309
2310                 if (meta == SEARCH(0)) {
2311                         if (anchor == NULL ||
2312                             !same_mpos(anchor->pos, pos) ||
2313                             anchor->searchlen != searchlen ||
2314                             !same_mp(anchor->curs.pos, curs.pos)) {
2315                                 struct search_anchor *a = xmalloc(sizeof(*a));
2316                                 a->pos = pos;
2317                                 a->row = row;
2318                                 a->start = start;
2319                                 a->curs = curs;
2320                                 a->searchlen = searchlen;
2321                                 a->notfound = search_notfound;
2322                                 a->next = anchor;
2323                                 anchor = a;
2324                         }
2325                 } else {
2326                         while (anchor) {
2327                                 struct search_anchor *a = anchor;
2328                                 anchor = a->next;
2329                                 free(a);
2330                         }
2331                 }
2332                 if (refresh == 3) {
2333                         /* move backward and forward to make sure we
2334                          * are on a visible line
2335                          */
2336                         tpos = pos;
2337                         prev_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2338                         if (tpos.p.m >= 0)
2339                                 pos = tpos;
2340                         tpos = pos;
2341                         next_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2342                         if (ci.merger[tpos.p.m].type != End)
2343                                 pos = tpos;
2344                 }
2345         }
2346 }
2347
2348 static int show_merge(char *origname, FILE *patch, int reverse,
2349                       int is_merge, char *before, char *after,
2350                       int replace, char *outfile,
2351                       int selftest, int ignore_blanks,
2352                       int just_diff, int backup)
2353 {
2354         struct plist p = {0};
2355
2356         p.file = origname;
2357         p.outfile = replace ? outfile : NULL;
2358         if (patch) {
2359                 p.start = 0;
2360                 fseek(patch, 0, SEEK_END);
2361                 p.end = ftell(patch);
2362                 fseek(patch, 0, SEEK_SET);
2363         }
2364         p.calced = 0;
2365         p.is_merge = is_merge;
2366         p.before = before;
2367         p.after = after;
2368
2369         freopen("/dev/null","w",stderr);
2370         return merge_window(&p, patch, reverse, replace, selftest,
2371                             ignore_blanks, just_diff, backup);
2372 }
2373
2374 static void calc_one(struct plist *pl, FILE *f, int reverse,
2375                      int ignore_blanks, int just_diff)
2376 {
2377         struct stream s1, s2;
2378         struct stream s = load_segment(f, pl->start, pl->end);
2379         struct stream sf;
2380         if (pl->is_merge) {
2381                 if (reverse)
2382                         split_merge(s, &sf, &s2, &s1);
2383                 else
2384                         split_merge(s, &sf, &s1, &s2);
2385                 pl->chunks = 0;
2386         } else {
2387                 if (reverse)
2388                         pl->chunks = split_patch(s, &s2, &s1);
2389                 else
2390                         pl->chunks = split_patch(s, &s1, &s2);
2391                 if (just_diff)
2392                         sf = s1;
2393                 else
2394                         sf = load_file(pl->file);
2395         }
2396         if (sf.body == NULL || s1.body == NULL || s1.body == NULL) {
2397                 pl->wiggles = pl->conflicts = -1;
2398         } else {
2399                 struct file ff, fp1, fp2;
2400                 struct csl *csl1, *csl2;
2401                 struct ci ci;
2402                 ff = split_stream(sf, ByWord | ignore_blanks);
2403                 fp1 = split_stream(s1, ByWord | ignore_blanks);
2404                 fp2 = split_stream(s2, ByWord | ignore_blanks);
2405                 if (pl->chunks && !just_diff)
2406                         csl1 = pdiff(ff, fp1, pl->chunks);
2407                 else
2408                         csl1 = diff(ff, fp1, 1);
2409                 csl2 = diff_patch(fp1, fp2, 1);
2410                 ci = make_merger(ff, fp1, fp2, csl1, csl2, 0, 1, 0);
2411                 pl->wiggles = ci.wiggles;
2412                 pl->conflicts = ci.conflicts;
2413                 free(ci.merger);
2414                 free(csl1);
2415                 free(csl2);
2416                 free(ff.list);
2417                 free(fp1.list);
2418                 free(fp2.list);
2419         }
2420
2421         free(s1.body);
2422         free(s2.body);
2423         free(s.body);
2424         if (!just_diff)
2425                 free(sf.body);
2426         pl->calced = 1;
2427 }
2428
2429 static int get_prev(int pos, struct plist *pl, int n, int mode)
2430 {
2431         int found = 0;
2432         if (pos == -1 || pl == NULL)
2433                 return pos;
2434         do {
2435                 if (pl[pos].prev == -1)
2436                         return pl[pos].parent;
2437                 pos = pl[pos].prev;
2438                 while (pl[pos].open &&
2439                        pl[pos].last >= 0)
2440                         pos = pl[pos].last;
2441                 if (pl[pos].last >= 0)
2442                         /* always see directories */
2443                         found = 1;
2444                 else if (mode == 0)
2445                         found = 1;
2446                 else if (mode <= 1 && pl[pos].wiggles > 0)
2447                         found = 1;
2448                 else if (mode <= 2 && pl[pos].conflicts > 0)
2449                         found = 1;
2450         } while (pos >= 0 && !found);
2451         return pos;
2452 }
2453
2454 static int get_next(int pos, struct plist *pl, int n, int mode,
2455                     FILE *f, int reverse, int ignore_blanks, int just_diff)
2456 {
2457         int found = 0;
2458         if (pos == -1)
2459                 return pos;
2460         do {
2461                 if (pl[pos].open) {
2462                         if (pos + 1 < n)
2463                                 pos =  pos+1;
2464                         else
2465                                 return -1;
2466                 } else {
2467                         while (pos >= 0 && pl[pos].next == -1)
2468                                 pos = pl[pos].parent;
2469                         if (pos >= 0)
2470                                 pos = pl[pos].next;
2471                 }
2472                 if (pos < 0)
2473                         return -1;
2474                 if (pl[pos].calced == 0 && pl[pos].end)
2475                         calc_one(pl+pos, f, reverse, ignore_blanks, just_diff);
2476                 if (pl[pos].last >= 0)
2477                         /* always see directories */
2478                         found = 1;
2479                 else if (mode == 0)
2480                         found = 1;
2481                 else if (mode <= 1 && pl[pos].wiggles > 0)
2482                         found = 1;
2483                 else if (mode <= 2 && pl[pos].conflicts > 0)
2484                         found = 1;
2485         } while (pos >= 0 && !found);
2486         return pos;
2487 }
2488
2489 static void draw_one(int row, struct plist *pl, FILE *f, int reverse,
2490                      int ignore_blanks, int just_diff)
2491 {
2492         char hdr[2*12];
2493         hdr[0] = 0;
2494
2495         if (pl == NULL) {
2496                 move(row, 0);
2497                 clrtoeol();
2498                 return;
2499         }
2500         if (pl->calced == 0 && pl->end)
2501                 /* better load the patch and count the chunks */
2502                 calc_one(pl, f, reverse, ignore_blanks, just_diff);
2503         if (pl->end == 0) {
2504                 strcpy(hdr, "         ");
2505         } else {
2506                 if (pl->chunks > 99)
2507                         strcpy(hdr, "XX");
2508                 else
2509                         sprintf(hdr, "%2d", pl->chunks);
2510                 if (pl->wiggles > 99)
2511                         strcpy(hdr+2, " XX");
2512                 else
2513                         sprintf(hdr+2, " %2d", pl->wiggles);
2514                 if (pl->conflicts > 99)
2515                         strcpy(hdr+5, " XX ");
2516                 else
2517                         sprintf(hdr+5, " %2d ", pl->conflicts);
2518         }
2519         if (pl->end)
2520                 strcpy(hdr+9, "= ");
2521         else if (pl->open)
2522                 strcpy(hdr+9, "+ ");
2523         else
2524                 strcpy(hdr+9, "- ");
2525
2526         if (!pl->end)
2527                 attrset(0);
2528         else if (pl->is_merge)
2529                 attrset(a_saved);
2530         else if (pl->conflicts)
2531                 attrset(a_has_conflicts);
2532         else if (pl->wiggles)
2533                 attrset(a_has_wiggles);
2534         else
2535                 attrset(a_no_wiggles);
2536
2537         mvaddstr(row, 0, hdr);
2538         mvaddstr(row, 11, pl->file);
2539         clrtoeol();
2540 }
2541
2542 static int save_one(FILE *f, struct plist *pl, int reverse,
2543                     int ignore_blanks, int backup)
2544 {
2545         struct stream sp, sa, sb, sm;
2546         struct file fa, fb, fm;
2547         struct csl *csl1, *csl2;
2548         struct ci ci;
2549         int chunks;
2550         sp = load_segment(f, pl->start,
2551                           pl->end);
2552         if (reverse)
2553                 chunks = split_patch(sp, &sa, &sb);
2554         else
2555                 chunks = split_patch(sp, &sb, &sa);
2556         fb = split_stream(sb, ByWord | ignore_blanks);
2557         fa = split_stream(sa, ByWord | ignore_blanks);
2558         sm = load_file(pl->file);
2559         fm = split_stream(sm, ByWord | ignore_blanks);
2560         csl1 = pdiff(fm, fb, chunks);
2561         csl2 = diff_patch(fb, fa, 1);
2562         ci = make_merger(fm, fb, fa, csl1, csl2, 0, 1, 0);
2563         return save_merge(fm, fb, fa, ci.merger,
2564                           pl->file, backup);
2565 }
2566
2567 static char *main_help[] = {
2568         "   You are using the \"browse\" mode of wiggle.",
2569         "This page shows a list of files in a patch together with",
2570         "the directories that contain them.",
2571         "A directory is indicated by a '+' if the contents are",
2572         "listed or a '-' if the contents are hidden.  A file is",
2573         "indicated by an '='.  Typing <space> or <return> will",
2574         "expose or hide a directory, and will visit a file.",
2575         "",
2576         "The three columns of numbers are:",
2577         "  Ch   The number of patch chunks which applied to",
2578         "       this file",
2579         "  Wi   The number of chunks that needed to be wiggled",
2580         "       in to place",
2581         "  Co   The number of chunks that created an unresolvable",
2582         "       conflict",
2583         "",
2584         "Keystrokes recognised in this page are:",
2585         "  ?          Display this help",
2586         "  SPC        On a directory, toggle hiding of contents",
2587         "             On file, visit the file",
2588         "  RTN        Same as SPC",
2589         "  q          Quit program",
2590         "  control-C  Disable auto-save-on-exit",
2591         "  n,j,DOWN   Go to next line",
2592         "  p,k,UP     Go to previous line",
2593         "",
2594         "  A          list All files",
2595         "  W          only list files with a wiggle or a conflict",
2596         "  C          only list files with a conflict",
2597         "",
2598         "  S          Save this file with changes applied.  If",
2599         "             some but not all files are saved, wiggle will",
2600         "             prompt on exit to save the rest.",
2601         "  R          Revert the current saved file to its original",
2602         "             content",
2603         "  I          toggle whether spaces are ignored",
2604         "             when matching text.",
2605         NULL
2606 };
2607 static char *saveall_msg = " %d file%s (of %d) have not been saved.";
2608 static char saveall_buf[200];
2609 static char *saveall_query[] = {
2610         "",
2611         saveall_buf,
2612         " Would you like to save them?",
2613         "  Y = yes, save them all",
2614         "  N = no, exit without saving anything else",
2615         "  Q = Don't quit just yet",
2616         NULL
2617 };
2618 static void main_window(struct plist *pl, int np, FILE *f, int reverse,
2619                         int replace, int ignore_blanks, int just_diff, int backup)
2620 {
2621         /* The main window lists all files together with summary information:
2622          * number of chunks, number of wiggles, number of conflicts.
2623          * The list is scrollable
2624          * When a entry is 'selected', we switch to the 'file' window
2625          * The list can be condensed by removing files with no conflict
2626          * or no wiggles, or removing subdirectories
2627          *
2628          * We record which file in the list is 'current', and which
2629          * screen line it is on.  We try to keep things stable while
2630          * moving.
2631          *
2632          * Counts are printed before the name using at most 2 digits.
2633          * Numbers greater than 99 are XX
2634          * Ch Wi Co File
2635          * 27 5   1 drivers/md/md.c
2636          *
2637          * A directory show the sum in all children.
2638          *
2639          * Commands:
2640          *  select:  enter, space, mouseclick
2641          *      on file, go to file window
2642          *      on directory, toggle open
2643          *  up:  k, p, control-p uparrow
2644          *      Move to previous open object
2645          *  down: j, n, control-n, downarrow
2646          *      Move to next open object
2647          *
2648          *  A W C: select All Wiggles or Conflicts
2649          *         mode
2650          *
2651          */
2652         char *mesg = NULL;
2653         char mesg_buf[1024];
2654         int last_mesg_len = 0;
2655         int pos = 0; /* position in file */
2656         int row = 1; /* position on screen */
2657         int rows = 0; /* size of screen in rows */
2658         int cols = 0;
2659         int tpos, i;
2660         int refresh = 2;
2661         int c = 0;
2662         int mode = 0; /* 0=all, 1= only wiggled, 2=only conflicted */
2663         int cnt; /* count of files that need saving */
2664         int any; /* count of files that have been save*/
2665         int ans;
2666         MEVENT mevent;
2667         char *debug = getenv("WIGGLE_DEBUG");
2668
2669         if (debug && !*debug)
2670                 debug = NULL;
2671
2672         freopen("/dev/null","w",stderr);
2673         term_init(1);
2674
2675         while (1) {
2676                 if (refresh == 2) {
2677                         clear(); (void)attrset(0);
2678                         attron(A_BOLD);
2679                         mvaddstr(0, 0, "Ch Wi Co Patched Files");
2680                         attroff(A_BOLD);
2681                         if (ignore_blanks)
2682                                 addstr(" (ignoring blanks)");
2683                         move(2, 0);
2684                         refresh = 1;
2685                 }
2686                 if (row < 1  || row >= rows)
2687                         refresh = 1;
2688                 if (refresh) {
2689                         refresh = 0;
2690                         getmaxyx(stdscr, rows, cols);
2691
2692                         if (row >= rows + 3)
2693                                 row = (rows+1)/2;
2694                         if (row >= rows)
2695                                 row = rows-1;
2696                         tpos = pos;
2697                         for (i = row; i > 1; i--) {
2698                                 tpos = get_prev(tpos, pl, np, mode);
2699                                 if (tpos == -1) {
2700                                         row = row - i + 1;
2701                                         break;
2702                                 }
2703                         }
2704                         /* Ok, row and pos could be trustworthy now */
2705                         tpos = pos;
2706                         for (i = row; i >= 1; i--) {
2707                                 draw_one(i, &pl[tpos], f, reverse, ignore_blanks, just_diff);
2708                                 tpos = get_prev(tpos, pl, np, mode);
2709                         }
2710                         tpos = pos;
2711                         for (i = row+1; i < rows; i++) {
2712                                 tpos = get_next(tpos, pl, np, mode, f, reverse,ignore_blanks, just_diff);
2713                                 if (tpos >= 0)
2714                                         draw_one(i, &pl[tpos], f, reverse, ignore_blanks, just_diff);
2715                                 else
2716                                         draw_one(i, NULL, f, reverse, ignore_blanks, just_diff);
2717                         }
2718                 }
2719                 attrset(0);
2720                 if (last_mesg_len) {
2721                         move(0, cols - last_mesg_len);
2722                         clrtoeol();
2723                         last_mesg_len = 0;
2724                 }
2725                 if (mesg) {
2726                         last_mesg_len = strlen(mesg);
2727                         move(0, cols - last_mesg_len);
2728                         addstr(mesg);
2729                         mesg = NULL;
2730                 } else if (debug) {
2731                         /* debugging help: report last keystroke */
2732                         char bb[30];
2733                         sprintf(bb, "last-key = 0%o", c);
2734                         attrset(0);
2735                         last_mesg_len = strlen(bb);
2736                         mvaddstr(0, cols - last_mesg_len, bb);
2737                 }
2738                 move(row, 9);
2739                 c = getch();
2740                 switch (c) {
2741                 case 'j':
2742                 case 'n':
2743                 case 'N':
2744                 case 'N'-64:
2745                 case KEY_DOWN:
2746                         tpos = get_next(pos, pl, np, mode, f, reverse, ignore_blanks, just_diff);
2747                         if (tpos >= 0) {
2748                                 pos = tpos;
2749                                 row++;
2750                         }
2751                         break;
2752                 case 'k':
2753                 case 'p':
2754                 case 'P':
2755                 case 'P'-64:
2756                 case KEY_UP:
2757                         tpos = get_prev(pos, pl, np, mode);
2758                         if (tpos >= 0) {
2759                                 pos = tpos;
2760                                 row--;
2761                         }
2762                         break;
2763
2764                 case KEY_MOUSE:
2765                         if (getmouse(&mevent) != OK)
2766                                 break;
2767                         while (row < mevent.y &&
2768                                (tpos = get_next(pos, pl, np, mode, f, reverse, ignore_blanks, just_diff))
2769                                >= 0) {
2770                                 pos = tpos;
2771                                 row++;
2772                         }
2773                         while (row > mevent.y &&
2774                                (tpos = get_prev(pos, pl, np, mode)) >= 0) {
2775                                 pos = tpos;
2776                                 row--;
2777                         }
2778                         if (row != mevent.y)
2779                                 /* couldn't find the line */
2780                                 break;
2781                         /* FALL THROUGH */
2782                 case ' ':
2783                 case 13:
2784                         if (pl[pos].end == 0) {
2785                                 pl[pos].open = !pl[pos].open;
2786                                 refresh = 1;
2787                                 if (pl[pos].open)
2788                                         mesg = "Opened folder";
2789                                 else
2790                                         mesg = "Closed folder";
2791                         } else {
2792                                 int c;
2793                                 if (pl[pos].is_merge)
2794                                         c = merge_window(&pl[pos], NULL, reverse, 0, 0,
2795                                                          ignore_blanks, just_diff, backup);
2796                                 else
2797                                         c = merge_window(&pl[pos], f, reverse, 0, 0,
2798                                                          ignore_blanks, just_diff, backup);
2799                                 refresh = 2;
2800                                 if (c) {
2801                                         pl[pos].is_merge = 1;
2802                                         snprintf(mesg_buf, cols,
2803                                                  "Saved file %s.",
2804                                                  pl[pos].file);
2805                                         mesg = mesg_buf;
2806                                 }
2807                         }
2808                         break;
2809                 case 27: /* escape */
2810                         attrset(0);
2811                         mvaddstr(0, cols-10, "ESC..."); clrtoeol();
2812                         c = getch();
2813                         switch (c) {
2814                         }
2815                         move(0, cols-10); clrtoeol();
2816                         break;
2817                 case 'C'-64:
2818                         if (replace)
2819                                 mesg = "Save-on-exit disabled. Use 'q' to quit.";
2820                         else
2821                                 mesg = "Use 'q' to quit.";
2822                         replace = 0;
2823                         break;
2824
2825                 case 'q':
2826                         cnt = 0;
2827                         any = 0;
2828                         for (i = 0; i < np; i++)
2829                                 if (pl[i].end && !pl[i].is_merge)
2830                                         cnt++;
2831                                 else if (pl[i].end)
2832                                         any++;
2833                         if (!cnt) {
2834                                 endwin();
2835                                 return;
2836                         }
2837                         refresh = 2;
2838                         if (replace)
2839                                 ans = 1;
2840                         else if (any) {
2841                                 sprintf(saveall_buf, saveall_msg,
2842                                         cnt, cnt == 1 ? "" : "s", cnt+any);
2843                                 ans = help_window(saveall_query, NULL, 1);
2844                         } else
2845                                 ans = 0;
2846                         if (ans < 0)
2847                                 break;
2848                         if (ans) {
2849                                 for (i = 0; i < np; i++) {
2850                                         if (pl[i].end
2851                                             && !pl[i].is_merge)
2852                                                 save_one(f, &pl[i],
2853                                                          reverse,
2854                                                          ignore_blanks, backup);
2855                                 }
2856                         } else
2857                                 cnt = 0;
2858                         endwin();
2859                         if (cnt)
2860                                 printf("%d file%s saved\n", cnt,
2861                                        cnt == 1 ? "" : "s");
2862                         return;
2863
2864                 case 'A':
2865                         mode = 0; refresh = 1;
2866                         mesg = "Showing ALL files";
2867                         break;
2868                 case 'W':
2869                         mode = 1; refresh = 1;
2870                         mesg = "Showing Wiggled files";
2871                         break;
2872                 case 'C':
2873                         mode = 2; refresh = 1;
2874                         mesg = "Showing Conflicted files";
2875                         break;
2876
2877                 case 'S': /* Save updated file */
2878                         if (pl[pos].end == 0) {
2879                                 /* directory */
2880                                 mesg = "Cannot save a folder.";
2881                         } else if (pl[pos].is_merge) {
2882                                 /* Already saved */
2883                                 mesg = "File is already saved.";
2884                         } else {
2885                                 if (save_one(f, &pl[pos], reverse, ignore_blanks, backup) == 0) {
2886                                         pl[pos].is_merge = 1;
2887                                         snprintf(mesg_buf, cols,
2888                                                  "Saved file %s.",
2889                                                  pl[pos].file);
2890                                         pl[pos].chunks = pl[pos].conflicts;
2891                                         pl[pos].wiggles = 0;
2892                                 } else
2893                                         snprintf(mesg_buf, cols,
2894                                                  "Failed to save file %s.",
2895                                                  pl[pos].file);
2896                                 mesg = mesg_buf;
2897                                 refresh = 1;
2898                         }
2899                         break;
2900
2901                 case 'R': /* Restore updated file */
2902                         if (pl[pos].end == 0)
2903                                 mesg = "Cannot restore a folder.";
2904                         else if (!pl[pos].is_merge)
2905                                 mesg = "File has not been saved, cannot restore.";
2906                         else if (!backup)
2907                                 mesg = "Backups are disabled, nothing to restore!";
2908                         else {
2909                                 /* rename foo.porig to foo, and clear is_merge */
2910                                 char *file = pl[pos].file;
2911                                 char *orignew = xmalloc(strlen(file) + 20);
2912                                 strcpy(orignew, file);
2913                                 strcat(orignew, ".porig");
2914                                 if (rename(orignew, file) == 0) {
2915                                         mesg = "File has been restored.";
2916                                         pl[pos].is_merge = 0;
2917                                         refresh = 1;
2918                                         calc_one(&pl[pos], f, reverse, ignore_blanks, just_diff);
2919                                 } else
2920                                         mesg = "Could not restore file!";
2921                         }
2922                         break;
2923
2924                 case 'I': /* Toggle ignoring blanks */
2925                         ignore_blanks = ignore_blanks ? 0 : IgnoreBlanks;
2926                         refresh = 2;
2927                         for (i = 0; i < np; i++)
2928                                 pl[i].calced = 0;
2929                         break;
2930
2931                 case '?':
2932                         help_window(main_help, NULL, 0);
2933                         refresh = 2;
2934                         break;
2935
2936                 case KEY_RESIZE:
2937                         refresh = 2;
2938                         break;
2939                 }
2940         }
2941 }
2942
2943 static void catch(int sig)
2944 {
2945         if (sig == SIGINT && !intr_kills) {
2946                 signal(sig, catch);
2947                 return;
2948         }
2949         noraw();
2950         nl();
2951         endwin();
2952         printf("Died on signal %d\n", sig);
2953         fflush(stdout);
2954         if (sig != SIGBUS && sig != SIGSEGV)
2955                 exit(2);
2956         else
2957                 /* Otherwise return and die */
2958                 signal(sig, NULL);
2959 }
2960
2961 static void term_init(int doraw)
2962 {
2963
2964         static int init_done = 0;
2965
2966         if (init_done)
2967                 return;
2968         init_done = 1;
2969
2970         signal(SIGINT, catch);
2971         signal(SIGQUIT, catch);
2972         signal(SIGTERM, catch);
2973         signal(SIGBUS, catch);
2974         signal(SIGSEGV, catch);
2975
2976         initscr();
2977         if (doraw)
2978                 raw();
2979         else
2980                 cbreak();
2981         noecho();
2982         start_color();
2983         use_default_colors();
2984         if (!has_colors()) {
2985                 a_delete = A_UNDERLINE;
2986                 a_added = A_BOLD;
2987                 a_common = A_NORMAL;
2988                 a_sep = A_STANDOUT;
2989                 a_already = A_STANDOUT;
2990                 a_has_conflicts = A_UNDERLINE;
2991                 a_has_wiggles = A_BOLD;
2992                 a_no_wiggles = A_NORMAL;
2993         } else {
2994                 init_pair(1, COLOR_RED, -1);
2995                 a_delete = COLOR_PAIR(1);
2996                 init_pair(2, COLOR_GREEN, -1);
2997                 a_added = COLOR_PAIR(2);
2998                 a_common = A_NORMAL;
2999                 init_pair(3, COLOR_WHITE, COLOR_GREEN);
3000                 a_sep = COLOR_PAIR(3); a_sep = A_STANDOUT;
3001                 init_pair(4, -1, COLOR_YELLOW);
3002                 a_void = COLOR_PAIR(4);
3003                 init_pair(5, COLOR_BLUE, -1);
3004                 a_unmatched = COLOR_PAIR(5);
3005                 init_pair(6, COLOR_CYAN, -1);
3006                 a_extra = COLOR_PAIR(6);
3007
3008                 init_pair(7, COLOR_BLACK, COLOR_CYAN);
3009                 a_already = COLOR_PAIR(7);
3010
3011                 a_has_conflicts = a_delete;
3012                 a_has_wiggles = a_added;
3013                 a_no_wiggles = a_unmatched;
3014                 a_saved = a_extra;
3015         }
3016         nonl(); intrflush(stdscr, FALSE); keypad(stdscr, TRUE);
3017         mousemask(ALL_MOUSE_EVENTS, NULL);
3018 }
3019
3020 int vpatch(int argc, char *argv[], int patch, int strip,
3021            int reverse, int replace, char *outfilename,
3022            int selftest, int ignore_blanks, int backup)
3023 {
3024         /* NOTE argv[0] is first arg...
3025          * Behaviour depends on number of args and 'patch'.
3026          * If 'patch' is '1', assume a patch. if '2', assume a diff.
3027          * 0: A multi-file patch or diff is read from stdin.
3028          *    A 'patch' is applies to relevant files. A 'diff' is just
3029          *    displayed.
3030          * 1: if 'patch', parse it as a multi-file patch/diff and allow
3031          *    the files to be browsed.
3032          *    if filename ends '.rej', then treat it as a patch/diff again
3033          *    a file with the same basename
3034          *    Else treat the file as a merge (with conflicts) and view it.
3035          *
3036          * 2: First file is original, second is patch unless patch==2,
3037          *    then two files need to be diffed.
3038          * 3: Files are: original previous new.  The diff between 'previous' and
3039          *    'new' needs to be applied to 'original'.
3040          *
3041          * If a multi-file patch is being read, 'strip' tells how many
3042          * path components to strip.  If it is -1, we guess based on
3043          * existing files.
3044          * If 'reverse' is given, when we invert any patch or diff
3045          * If 'replace' then we save the resulting merge.
3046          */
3047         FILE *in;
3048         FILE *f;
3049         struct plist *pl;
3050         int num_patches;
3051         int just_diff = (patch == 2);
3052
3053         switch (argc) {
3054         default:
3055                 fprintf(stderr, "%s: too many file names given.\n", Cmd);
3056                 exit(1);
3057
3058         case 0: /* stdin is a patch or diff */
3059                 if (lseek(fileno(stdin), 0L, 1) == -1) {
3060                         /* cannot seek, so need to copy to a temp file */
3061                         f = tmpfile();
3062                         if (!f) {
3063                                 fprintf(stderr, "%s: Cannot create temp file\n", Cmd);
3064                                 exit(1);
3065                         }
3066                         pl = parse_patch(stdin, f, &num_patches);
3067                         in = f;
3068                 } else {
3069                         pl = parse_patch(stdin, NULL, &num_patches);
3070                         in = fdopen(dup(0), "r");
3071                 }
3072                 /* use stderr for keyboard input */
3073                 dup2(2, 0);
3074                 if (!just_diff &&
3075                     set_prefix(pl, num_patches, strip) == 0) {
3076                         fprintf(stderr, "%s: aborting\n", Cmd);
3077                         exit(2);
3078                 }
3079                 pl = sort_patches(pl, &num_patches);
3080                 main_window(pl, num_patches, in, reverse, replace, ignore_blanks,
3081                             just_diff, backup);
3082                 plist_free(pl, num_patches);
3083                 fclose(in);
3084                 break;
3085
3086         case 1: /* a patch/diff, a .rej, or a merge file */
3087                 f = fopen(argv[0], "r");
3088                 if (!f) {
3089                         fprintf(stderr, "%s: cannot open %s\n", Cmd, argv[0]);
3090                         exit(1);
3091                 }
3092                 check_dir(argv[0], fileno(f));
3093                 if (patch) {
3094                         pl = parse_patch(f, NULL, &num_patches);
3095                         if (!just_diff && set_prefix(pl, num_patches, strip) == 0) {
3096                                 fprintf(stderr, "%s: aborting\n", Cmd);
3097                                 exit(2);
3098                         }
3099                         pl = sort_patches(pl, &num_patches);
3100                         main_window(pl, num_patches, f, reverse, replace,
3101                                     ignore_blanks, just_diff, backup);
3102                         plist_free(pl, num_patches);
3103                 } else if (strlen(argv[0]) > 4 &&
3104                          strcmp(argv[0]+strlen(argv[0])-4, ".rej") == 0) {
3105                         char *origname = strdup(argv[0]);
3106                         origname[strlen(origname) - 4] = '\0';
3107                         show_merge(origname, f, reverse, 0, NULL, NULL,
3108                                    replace, outfilename,
3109                                    selftest, ignore_blanks, just_diff, backup);
3110                 } else
3111                         show_merge(argv[0], f, reverse, 1, NULL, NULL,
3112                                    replace, outfilename,
3113                                    selftest, ignore_blanks, just_diff, backup);
3114
3115                 break;
3116         case 2: /* an orig and a diff/.rej  or two files */
3117                 if (just_diff) {
3118                         show_merge(NULL, NULL, reverse, 0, argv[0], argv[1],
3119                                    replace, outfilename,
3120                                    selftest, ignore_blanks, just_diff, backup);
3121                         break;
3122                 }
3123                 f = fopen(argv[1], "r");
3124                 if (!f) {
3125                         fprintf(stderr, "%s: cannot open %s\n", Cmd, argv[0]);
3126                         exit(1);
3127                 }
3128                 check_dir(argv[1], fileno(f));
3129                 show_merge(argv[0], f, reverse, 0, NULL, NULL,
3130                            replace, outfilename,
3131                            selftest, ignore_blanks, just_diff, backup);
3132                 break;
3133         case 3: /* orig, before, after */
3134                 show_merge(argv[0], NULL, reverse, 0, argv[1], argv[2],
3135                            replace, outfilename,
3136                            selftest, ignore_blanks, just_diff, backup);
3137                 break;
3138         }
3139
3140         noraw();
3141         nl();
3142         endwin();
3143         exit(0);
3144 }